TL;DR:
Check out this repo for a contract that simplifies onchain bitmap generation
Check out this contract if you want an example of how to use BitmapRendererV1
Use this tool if you want to extract the palette + pixel data from a bitmap or png image
All made by yours truly 😃
If you find this useful and you wanna show your support, buy an Inky (0.002 ETH)! It's the first project that uses BitmapRendererV1 (that I know of), and you get to inscribe your wallet address on the Ink chain forever.
Preface
The idea of onchain art has fascinated me for a while. Before I started diving into crypto, I assumed NFT art was all onchain. The idea of buying an NFT whose image lives on AWS where it can be lost if a payment is missed, or even IPFS where persistence isn't guaranteed just feels off.
Beyond just image storage, I'm particularly interested in image generation. It's easy to take an image file, base64 encode it, and store it in a contract as a string, but I like the idea of images being created from within the EVM itself.
If this sort of thing interests you too, you might enjoy this post.
Origins
This past year, I've been working on a couple NFT projects with a pixel art aspect (BlockSocks, where artists can design and sell socks; and InkyOnchain, an NFT collection to commemorate the launch of Kraken's new L2), so naturally I started thinking about how to store the image data onchain.
Spoiler: BlockSocks' images aren't onchain (yet, they will be but that's a whole different blog post), and Inky's are. While trying to get BlockSocks' images onchain, I began exploring if using the bitmap file format was feasible. This led to the creation of BitmapRendererV1.sol, a smart contract that implements the bitmap spec and has abstractions that make it easy to generate pixel art images from within other smart contracts.
If you're eager to start building, you can go look at the repo, read the README, and use BitmapRendererV1 right now (so long as you're building on Base or Ink). But if you want to learn more about onchain images, the bitmap spec, generating dynamic images, or just like conversational technical writing, read on.
First, some color around onchain images.
SVGs: The golden child of onchain artists
If you're working on a fully onchain NFT project and you're looking for ways to store image data, there's one file type that will be mentioned over and over again: SVGs. It makes sense, SVGs are written in the text-based XML format, and storing text onchain is dead simple. SVGs are also very human-readable, at least as far as image formats go.
There's limitations with SVGs, though, especially when it comes to onchain art. They can get pretty verbose, especially with more complex artwork. If you're deploying on Ethereum, storing long strings of text can get expensive.
There's another issue you might run into if your SVGs get too wordy, and that is the transaction gas limit. There's a finite amount of gas a transaction can consume, and one of the things you have to do to serve onchain art is base64 encode the image data. Base64 encoding a large string can eat up a lot of gas, and if you hit the gas limit, well, that's just not going to work.
That said, SVGs can be a great solution, especially if your artwork lends itself to the vector format. If it does, check out this post from the Base team for a tutorial on onchain SVGs.
But if your artwork is more of the raster flavor, especially if it's pixel art, there's another image format that works well onchain: Bitmap images.
Bitmaps: A strong candidate for an alternative format
So why are bitmaps well suited for onchain image generation? Two things:
The spec is relatively straightforward
Compare the overall length of the bitmap spec above to the PNG spec to get a sense of how much simpler bitmaps are.
It doesn't require compression
While bitmap images can be compressed, it's not necessary. Compression requires a lot more operations to occur which can quickly increase the amount of gas needed for image generation. This is why the GIF image format isn't suited for onchain generation, as it requires a compression step for the file to be up to spec.
Generating bitmap images onchain
So, we have a viable file format for onchain image generation. How do we actually generate one? Just follow the spec! I kid, I kid 😜
Even though the spec is straightforward, it's not so easy to implement. The spec has building up a bytes array, a type that we're probably not all using day to day, and you have to push bytes into the array in a pretty specific way.
Also, if you write a lot of helper functions to make the task of creating this bytes array easier, you end up needing a lot of gas. I learned this first hand.
Here's where BitmapRendererV1
comes in. It's my attempt at abstracting the bitmap spec into something that's easy to use. It's my first stab at this, and I'll likely try to optimize things further later on to be more gas efficient.
Also, if you want to implement your own way of generating bitmaps onchain, I hope it's helpful to have a reference of how I did it.
Bitmap encoding schemes
If you look at the spec, you'll notice that there are multiple ways that bitmaps can be encoded. BitmapRendererV1
implements two of these:
Raster Data encoding for 8bit / 256 color images
This encoding is used by the
create8bitBMPData
functionThis format defines a color palette (up to 256 colors) and the image data represents the color of the pixels in the image as indices into that color palette
Raster Data encoding for 24bit / truecolor images
This encoding is used by the
create24bitBMPData
functionThis format does not use a color palette, and instead the image data is represents the color of the pixels in the image by storing the RGB values of each pixel
The 8bit format limits the number of colors that can be used but is much more space efficient, and the 24bit format lets you use any number of colors at the cost of higher storage. (Under specific circumstances the 24bit format could be more space efficient, but under most circumstances this holds true).
To visualize this, let's look at an example.
This is a 3px by 3px image scaled up 50x, but pretend we want to generate this as a 3x3 image.
8bit encoding
To create this image using create8bitBMPData
, you would do the following:
import "./IBitmapRendererV1.sol";
contract Example8bit {
IBitmapRendererV1 public bitmapRenderer = IBitmapRendererV1(address(0));
// Define the width and height of the image
uint8 width = 3;
uint8 height = 3;
// Define the colors that will be used in the image
uint256[] palette = [
0xFF0000,
0x00FF00,
0x0000FF,
0xFFFF00,
0xFF00FF,
0x00FFFF,
0xFFFFFF,
0x919292,
0x000000
];
// Define the image pixel data by specifying what color indices
// will make up the image, pixel by pixel, starting at the top left
// corner, and moving left to right, row by row, from top to bottom.
uint8[] pixelData = [0, 1, 2, 3, 4, 5, 6, 7, 8];
function render() public view returns (bytes memory) {
// Create the image by passing its width, height,
// the palette colors, and the pixel data
return bitmapRenderer.create8bitBMPData(
width,
height,
palette,
pixelData
);
}
}
In short, you define a palette as an array of uint256
which represents the hex color values in 0xRRGGBB
, and you define the image data pixel by pixel, starting at the top left, and moving left to right. When you hit the end of the row, you go down one row, and start at the left again.
Fun fact, the bitmap spec actually has you starting at the bottom right and moving backwards, but I think starting at the top left is much more ergonomic.
24bit encoding
To create the same image using create24bitBMPData
, it would look like this:
import "./IBitmapRendererV1.sol";
contract Example24bit {
IBitmapRendererV1 public bitmapRenderer = IBitmapRendererV1(address(0));
// Define the width and height of the image
uint8 width = 3;
uint8 height = 3;
// Define the image pixel data by specifying what color hex values
// will make up the image, pixel by pixel, starting at the top left
// corner, and moving left to right, row by row, from top to bottom.
uint256[] pixelData24bit = [
0xFF0000, 0x00FF00, 0x0000FF,
0xFFFF00, 0xFF00FF, 0x00FFFF,
0xFFFFFF, 0x919292, 0x000000
];
function render() public view returns (bytes memory) {
// Create the image by passing its width, height, and pixel data
return bitmapRenderer.create24bitBMPData(width, height, pixelData24bit);
}
}
The main difference here is that we don't use a palette table. We instead specify the colors in the pixel data directly.
Using the generated images as NFT metadata
Each of the functions above returns a bytes
array, but when we're working with onchain NFT metadata, we need to base64-encode the data, and prefix it with its 'Data URI scheme': "data:image/bmp;base64,"
The BitmapRendererV1
contract includes functions to do this automatically for both 8bit and 24bit images. They are:
createBase64Encoded8bitBMP
createBase64Encoded24bitBMP
Each has the same signature as its non-base64-encoded counterpart, and simply returns the image data but base64-encoded and with the data URI scheme.
With these utility functions, we can get the image data in a format that will make it easy to use as onchain metadata. The code below shows a very simple example of how you can use createBase64Encoded24bitBMP
to generate a token's image and include it as onchain metadata.
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "./IBitmapRendererV1.sol";
contract Example24bitNFT is ERC721 {
IBitmapRendererV1 public bitmapRenderer = IBitmapRendererV1(address(0));
uint8 width = 3;
uint8 height = 3;
uint256[] pixelData24bit = [
0xFF0000,
0x00FF00,
0x0000FF,
0xFFFF00,
0xFF00FF,
0x00FFFF,
0xFFFFFF,
0x919292,
0x000000
];
constructor() ERC721("Example24bitNFT", "24BMP") {}
function mint() public {
_mint(msg.sender, 1);
}
function tokenURI(
uint256 _tokenId
) public view override returns (string memory) {
// Use `createBase64Encoded24bitBMP` to get the base64 encoded image
// with its data URI scheme prefix
string memory image = bitmapRenderer.createBase64Encoded24bitBMP(
width,
height,
pixelData24bit
);
// Create the onchain metadata object and include the image property
string memory json = string.concat(
"{",
'"name": "Onchain Bitmap #',
Strings.toString(_tokenId),
'",',
'"image": "',
image,
'"',
"}"
);
// Return the onchain metadata object as a base64 encoded string, with
// the "data:application/json;base64," data URI scheme
string memory dataUri = string.concat(
"data:application/json;base64,",
Base64.encode(bytes(json))
);
return dataUri;
}
}
Conclusion
I hope this was an interesting read, and that it either taught you something new, caused you to think about something differently, or inspired you to build something with an onchain image component.
I plan on continuing to explore how to render bitmaps onchain more efficiently so I can finally make the hamster PFP project that's in the header image.
I also plan on writing some related posts about some interesting things you can do with onchain image generation, like converting users' public address to color palettes, using bitmasks to manipulate images, and so on.
If you want to talk about any of this stuff further, hit me up on Warpcast at @typeof.eth! Thanks for reading :)
Bonus
Wanna play around with image generation real quick? Let's take this picture of Street Fighter's Chun-Li, deconstruct it into its palette/pixel data, and have the Base blockchain turn it back into an image, then render it in the browser.
I was going to ask you to download it and deconstruct it, but Paragraph converts the PNG into a WebP image, so I'll just deconstruct it for you using this tool. (You can use your own PNG or BMP image to do this as well!)
Copy the values below and paste them into the createBase64Encoded8bitBMP
readonly function in this Base contract.
The contract will return a long string of text. Highlight and copy that string, and then paste it into the textarea in this tool.
After pasting it, you should see Chun-Li show up!
Width: 64
Height: 64
Palette:
[0x5566AA,0x4455AA,0x6677AA,0x7788BB,0x336688,0x447788,0x558888,0x669988,0x997744,0x776633,0x777799,0xBB8855,0xDD9966,0xEEAA77,0x999999,0xAA66AA,0xFFCC88,0x8855AA,0xFFCC66,0xDDAA66,0xAACCFF,0x5588DD,0x77AAEE,0x550000,0x0055BB,0x666622,0xFFFFFF,0x774400,0x000000,0x446677,0x885500,0xAA6655,0xCC8866,0x000099,0xEE9977,0xFFEEAA,0xFFCC99,0x559911,0x558811,0x557711,0x555511,0x445511,0x334411,0x556611,0x223311,0x556677,0x557777,0x667766,0x446666,0x778866,0x889966,0x99AA77,0x447766,0x448877]
Pixel data:
[0,1,0,0,0,1,0,0,0,0,2,2,0,2,0,3,2,2,3,0,2,0,2,2,3,2,2,3,0,0,1,0,0,0,0,1,1,0,0,2,2,3,3,3,2,0,2,0,1,1,1,4,4,5,4,5,5,4,5,6,5,7,5,5,0,0,0,0,2,0,2,0,2,2,2,0,3,0,3,2,3,2,0,1,0,2,0,1,0,1,4,0,4,0,1,4,1,0,0,0,1,4,1,1,0,0,3,3,3,2,2,1,1,1,4,4,5,4,4,5,4,6,6,5,5,5,6,6,0,0,2,2,0,2,2,3,2,3,3,2,2,3,3,3,2,0,1,1,1,1,1,4,1,1,4,1,4,4,4,1,4,1,4,1,0,1,1,4,1,1,1,4,5,4,5,6,5,4,5,4,4,5,5,5,4,5,4,5,5,5,6,5,8,9,9,0,2,2,3,2,3,3,3,3,3,0,0,1,4,1,1,10,10,10,0,1,1,4,4,0,4,4,1,4,1,4,4,1,4,1,1,4,4,4,4,5,5,5,5,5,6,5,6,5,5,5,6,4,5,5,5,5,6,4,5,5,11,11,11,8,8,11,12,13,14,10,15,15,10,10,2,2,10,10,14,14,10,10,10,10,0,1,4,1,0,10,0,1,0,4,4,4,4,4,4,4,5,5,4,5,4,5,5,5,5,6,6,6,5,4,6,6,5,4,6,4,5,5,5,5,8,8,13,13,16,16,16,16,17,17,2,2,2,0,15,15,0,0,0,0,4,0,0,4,4,0,4,4,4,14,18,10,4,4,4,4,4,4,4,4,4,5,4,5,6,5,6,5,6,6,5,6,6,6,5,5,5,6,5,6,6,5,6,5,11,11,13,13,13,12,11,8,10,14,2,14,0,0,2,2,14,19,10,10,0,4,20,20,21,22,23,23,23,23,23,23,23,20,20,21,24,4,4,5,4,4,5,6,5,5,5,6,5,6,5,6,5,6,6,5,6,6,6,7,6,5,7,6,25,9,9,9,9,9,9,8,0,0,0,2,2,10,10,10,2,0,2,2,0,26,26,26,26,21,20,23,27,27,27,27,27,27,27,28,23,24,5,5,5,7,5,6,6,5,7,6,6,5,7,7,6,5,5,6,6,6,6,6,7,6,6,5,25,9,8,8,8,8,11,1,0,1,0,1,29,1,4,0,0,4,0,4,20,26,26,26,26,20,26,23,23,23,30,31,32,31,23,23,30,23,23,23,6,5,5,5,6,6,5,6,6,7,6,6,6,6,5,6,5,5,6,5,6,6,5,5,25,9,11,13,11,13,11,4,1,4,4,4,4,4,1,4,4,4,22,33,20,26,26,26,26,20,26,23,30,30,31,34,31,23,31,30,32,31,27,30,23,6,6,6,6,7,6,6,6,6,6,7,6,7,6,6,6,6,5,6,6,5,5,7,25,9,12,13,16,13,12,0,0,0,1,0,1,0,0,14,14,0,26,33,21,20,26,26,22,20,20,30,32,35,26,26,35,32,23,31,30,34,31,31,30,6,5,6,7,7,6,7,6,5,6,5,6,5,6,5,6,7,6,6,5,6,6,5,9,11,12,11,11,12,8,10,10,14,10,14,10,14,14,1,0,0,0,22,33,24,21,21,20,26,31,32,32,35,26,26,35,35,27,31,31,23,36,23,32,23,6,6,7,6,7,5,6,6,7,6,5,6,5,6,6,6,6,7,6,6,6,6,25,8,11,13,11,13,12,0,0,1,0,1,0,1,1,4,1,4,22,26,22,24,22,20,22,23,27,34,35,26,26,26,35,35,30,35,31,23,32,23,30,23,6,6,6,6,6,6,7,6,6,6,6,6,7,6,7,6,7,7,6,6,6,6,9,11,13,16,12,13,11,18,18,14,10,10,10,10,10,0,14,5,26,20,28,27,30,34,34,31,34,35,35,32,32,32,36,35,34,35,34,6,31,23,23,23,5,6,6,6,6,5,7,6,7,6,5,7,5,7,6,5,7,6,5,5,6,5,25,8,11,8,12,11,12,14,2,2,14,14,2,2,2,5,4,4,20,22,4,28,27,35,32,31,35,26,36,34,31,23,31,31,35,35,32,6,27,23,6,28,7,5,6,6,6,6,6,6,7,6,6,6,7,5,6,7,6,7,6,7,5,6,9,11,13,13,16,13,8,1,0,1,0,2,3,3,4,4,4,4,4,4,5,4,28,32,36,34,26,35,36,32,35,28,28,31,35,31,7,23,23,6,28,5,6,6,7,6,6,6,6,6,6,6,6,6,6,7,6,6,7,7,6,6,6,6,8,11,8,12,13,12,8,4,1,4,33,33,24,24,33,4,4,4,5,4,4,5,5,31,34,36,26,26,26,35,36,31,34,35,35,34,6,27,6,7,6,6,7,6,6,6,6,6,6,5,6,5,6,6,6,6,6,6,6,5,5,6,6,7,25,11,11,16,13,13,11,2,33,24,22,24,22,22,24,24,4,33,24,21,21,33,24,22,23,36,26,26,26,26,35,35,35,35,26,36,7,6,7,6,5,6,5,6,6,6,7,5,6,6,6,5,6,5,5,6,5,5,5,5,6,6,6,6,11,12,12,11,13,11,9,24,21,26,33,26,20,20,22,21,31,20,20,20,20,24,22,20,23,23,26,26,26,26,35,35,35,35,36,7,6,5,7,6,6,6,7,6,5,6,6,6,6,5,6,6,6,5,5,6,5,5,5,5,5,6,7,6,8,8,12,8,12,13,33,21,22,26,33,26,22,20,20,20,31,26,26,26,26,22,22,26,24,36,36,26,26,26,35,35,34,31,34,7,6,7,5,6,5,6,5,7,6,6,6,5,6,5,6,5,6,5,6,5,5,5,4,5,6,5,5,6,8,16,16,12,12,37,24,22,26,23,32,21,26,22,20,20,22,31,26,26,26,20,21,22,26,24,35,32,26,26,26,35,34,34,6,7,7,6,7,5,6,7,6,6,6,6,6,6,6,6,5,5,5,5,5,6,5,5,5,5,4,6,6,5,8,11,8,11,12,33,33,26,24,32,36,30,33,26,26,22,20,31,26,20,26,26,20,21,26,20,33,36,32,34,36,26,35,21,7,6,5,7,7,6,5,7,6,7,6,6,6,6,6,6,6,6,6,6,6,6,5,5,5,7,5,5,4,5,9,11,13,11,38,33,20,27,34,26,26,26,34,30,22,26,20,31,20,20,20,20,20,20,21,20,33,36,34,30,30,30,21,33,33,6,7,7,7,7,6,5,6,6,6,7,6,6,7,7,6,7,5,5,5,5,6,5,6,5,6,5,5,4,8,16,37,11,37,33,22,27,35,26,26,26,35,34,30,22,26,31,20,20,20,20,20,22,21,21,24,24,27,33,33,24,22,21,24,33,5,6,6,7,5,7,6,6,7,6,6,6,6,7,7,6,5,6,6,6,5,5,5,5,5,5,5,5,8,8,11,39,40,33,21,30,35,26,26,35,35,35,32,30,26,31,21,24,22,22,20,26,26,20,20,22,21,21,24,33,22,22,22,24,20,7,5,6,7,6,6,6,7,6,5,6,6,6,6,6,6,5,6,5,5,5,5,6,5,6,5,5,25,9,12,11,37,37,33,30,36,35,35,36,35,35,35,30,31,24,33,26,26,26,26,20,22,22,22,22,22,22,21,24,24,22,22,21,26,7,5,5,7,7,6,5,24,26,24,5,6,6,27,34,34,27,27,34,27,6,6,6,6,6,6,6,8,39,41,40,37,39,33,31,36,36,35,35,26,26,35,31,24,26,26,22,22,22,22,22,20,20,20,20,20,20,22,21,33,26,26,26,7,7,7,6,5,5,5,5,22,26,26,33,23,27,34,36,35,36,23,34,34,27,6,5,5,6,7,10,42,8,39,41,40,37,43,31,36,35,26,26,26,26,35,30,24,21,22,20,20,26,26,26,20,20,26,20,26,20,20,22,21,33,23,24,6,5,6,5,5,5,5,4,24,33,23,23,23,34,36,36,34,36,36,27,35,31,6,6,5,6,5,7,8,39,39,39,37,40,40,32,36,35,26,26,26,35,36,30,22,20,26,26,26,26,26,26,20,20,20,20,20,20,20,20,22,24,34,33,7,5,6,4,29,29,29,6,28,23,23,23,27,34,35,36,34,23,27,31,32,34,5,6,5,6,6,6,40,8,37,39,37,37,38,34,35,26,26,26,26,35,34,22,26,26,26,26,26,26,26,20,20,20,20,20,20,20,20,20,22,24,36,36,31,6,29,29,29,5,29,5,5,23,21,20,22,34,26,35,34,31,32,34,35,34,6,6,6,5,6,6,9,38,39,37,39,42,31,34,35,26,26,26,35,36,32,34,20,26,26,26,26,26,20,20,20,20,20,22,20,20,20,22,21,33,35,35,36,31,4,4,4,29,5,29,5,22,20,26,22,23,36,26,36,34,30,32,34,31,5,29,6,5,29,6,8,41,37,43,41,39,31,36,35,26,26,26,35,36,31,32,36,20,20,20,20,20,26,20,20,22,22,22,22,26,22,21,24,21,26,35,35,34,30,4,29,29,29,29,31,23,23,23,23,23,23,36,36,36,31,27,23,29,29,29,29,29,29,29,39,39,42,40,42,44,32,36,26,26,26,35,35,34,21,22,32,36,35,35,26,26,22,22,22,22,21,21,21,36,34,27,21,35,26,26,35,36,31,45,45,45,31,34,34,36,36,23,23,23,23,23,31,31,31,23,45,45,45,45,45,45,45,45,42,8,41,39,43,44,34,35,26,35,36,36,34,31,24,21,22,22,22,22,22,22,26,22,21,21,21,24,33,30,27,34,34,36,35,26,35,36,34,31,32,34,36,36,36,35,35,35,23,23,26,20,23,23,23,46,45,46,45,46,45,45,45,45,38,40,38,40,39,42,36,26,26,26,26,26,35,36,32,31,24,24,24,24,24,24,26,20,23,24,23,23,23,45,45,30,34,34,36,35,35,36,36,32,36,36,36,36,35,35,36,34,23,23,20,26,23,23,26,24,46,46,45,45,46,46,45,45,38,40,42,40,41,31,35,26,26,26,26,26,26,26,36,36,32,33,33,33,27,23,23,23,23,23,31,35,34,23,45,45,30,34,34,36,35,36,34,34,36,36,36,35,36,36,34,31,45,46,23,23,23,22,26,20,45,45,45,45,45,45,45,45,43,43,37,40,37,31,26,26,35,35,35,26,26,26,26,26,36,36,32,31,27,27,20,23,23,31,34,32,31,31,23,23,23,30,32,34,36,36,34,36,36,35,35,36,34,31,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,38,37,43,43,41,31,36,26,26,35,35,35,35,26,26,26,26,26,35,35,23,22,26,23,23,34,36,35,26,26,35,36,34,46,31,34,34,36,36,35,35,26,36,34,31,46,46,46,45,45,45,45,45,45,46,45,45,46,46,45,45,45,45,45,37,38,38,41,43,41,32,36,36,35,35,35,35,35,35,35,35,26,26,26,23,23,23,23,23,35,26,26,26,26,36,32,31,23,45,30,34,36,36,26,26,34,31,45,46,46,45,45,45,45,46,46,45,45,45,45,46,46,45,45,45,45,45,45,40,37,37,43,41,41,31,31,31,32,34,36,36,36,36,36,35,35,35,35,23,22,26,23,23,26,26,35,35,26,35,32,35,23,45,45,30,31,34,34,31,30,45,45,45,46,45,45,45,46,45,46,46,45,45,45,45,46,45,45,45,45,46,45,40,41,41,37,43,31,36,34,31,27,30,30,30,30,27,27,27,27,27,23,27,27,20,23,23,35,35,35,35,35,36,31,23,45,45,45,45,45,30,30,46,46,46,45,45,45,45,45,45,45,45,45,45,45,46,45,45,45,45,45,45,45,46,46,41,41,37,43,31,36,24,31,34,36,34,31,30,23,23,34,22,22,22,24,27,23,23,23,23,32,34,36,35,35,31,23,47,47,48,48,45,45,46,45,45,45,45,46,45,45,45,45,48,48,45,45,45,46,6,46,45,45,45,45,46,45,45,45,41,41,41,31,36,24,31,36,36,32,32,30,23,23,23,34,20,22,22,22,24,24,23,23,32,23,23,23,23,23,23,47,47,47,47,47,47,48,48,45,45,48,45,48,48,48,45,45,48,48,45,45,45,45,45,45,45,45,45,45,45,45,45,45,38,43,31,36,24,31,26,36,34,32,31,27,23,23,23,34,20,20,22,22,22,24,26,22,34,23,23,28,49,49,49,49,47,47,47,47,47,47,47,47,47,45,47,47,47,45,47,47,48,48,48,48,45,45,45,45,45,45,45,45,45,45,45,45,40,43,34,21,30,26,35,36,32,32,31,30,27,27,23,27,36,20,22,22,21,21,20,21,36,23,23,23,23,28,49,49,49,49,49,49,47,47,49,45,45,47,47,47,45,45,45,47,47,47,47,48,48,48,45,45,45,45,45,46,45,45,45,45,37,31,34,21,34,35,36,36,34,31,31,31,31,31,30,27,23,35,20,22,21,21,22,24,36,23,23,27,27,23,28,49,50,49,49,49,49,50,49,49,49,49,49,47,47,45,45,45,47,47,47,47,47,47,45,45,47,45,45,45,45,45,45,45,41,36,21,30,35,36,36,36,36,34,32,32,32,32,31,31,27,23,35,20,22,21,21,24,35,23,27,27,27,27,27,23,28,49,49,49,49,49,49,49,50,50,49,50,50,49,49,49,49,49,49,47,47,45,47,46,45,45,45,45,45,46,45,45,32,34,21,31,36,36,34,36,35,36,36,34,32,32,32,31,31,30,23,36,22,21,21,24,35,30,30,30,30,30,30,27,27,23,28,49,50,50,50,50,49,51,50,51,50,50,50,49,49,49,45,49,47,47,45,45,46,45,45,45,46,45,45,45,36,22,21,31,34,34,34,36,36,35,36,36,34,32,32,32,31,31,30,21,20,22,21,21,36,30,30,31,31,30,30,30,30,27,27,28,49,50,49,47,49,50,49,50,51,49,49,49,49,45,46,47,47,48,45,46,45,48,48,45,45,48,45,45,36,21,24,31,34,32,34,34,36,36,35,36,34,32,32,32,32,31,31,30,22,20,21,21,34,30,30,31,31,31,31,30,30,30,30,27,23,28,46,49,49,50,49,50,47,50,47,47,45,46,45,46,48,48,48,45,45,48,45,45,45,45,45,45,34,21,33,30,32,32,32,34,34,36,36,35,36,34,32,32,32,32,31,30,30,20,22,21,34,27,30,30,31,31,31,31,31,30,30,30,30,27,23,46,49,49,49,49,49,49,49,47,47,45,45,47,48,45,45,48,45,52,45,45,45,47,47,47,22,21,33,27,31,32,32,32,34,34,34,36,36,36,34,32,32,32,31,31,30,23,20,21,34,23,27,30,30,31,31,31,32,31,31,30,30,30,27,23,28,49,49,49,49,49,49,50,49,49,47,47,47,45,46,46,46,45,45,45,45,47,48,45,22,24,33,23,30,31,32,32,32,34,34,34,34,36,36,32,32,32,32,31,31,30,24,22,34,23,23,30,30,31,31,31,31,31,32,31,31,30,30,30,27,23,49,50,49,50,51,51,50,50,49,49,49,47,48,48,52,48,45,52,52,45,45,45,22,24,33,33,27,31,31,32,32,32,34,34,34,34,34,34,32,32,32,32,31,30,27,22,32,27,27,27,30,30,31,31,31,31,31,31,31,31,31,31,30,27,23,51,50,50,49,50,49,49,49,49,50,47,49,47,47,48,48,48,45,45,45,45,22,24,33,33,23,30,31,31,32,32,32,32,34,34,34,34,34,32,32,32,31,31,30,27,32,30,31,30,27,30,30,31,31,31,32,31,32,31,32,31,31,31,27,23,49,49,49,49,49,49,50,50,49,49,49,49,50,49,47,47,47,47,47,47,22,24,33,33,23,27,30,31,31,32,32,32,32,32,34,34,34,32,32,32,32,31,30,27,23,30,30,30,31,30,30,30,31,31,31,31,31,31,31,32,31,32,31,27,23,49,50,49,50,50,50,50,50,50,51,51,50,50,50,49,49,49,47,47,22,24,33,33,24,23,27,30,30,31,32,32,32,32,32,34,34,34,32,32,32,31,31,27,27,23,27,30,30,30,30,30,30,31,31,31,31,31,32,31,32,32,32,31,27,23,50,50,50,51,51,50,50,51,50,50,50,49,50,49,50,49,49,49,22,24,33,33,24,21,23,27,27,31,31,32,32,32,32,32,32,34,32,32,32,32,31,30,30,23,23,27,27,27,30,30,30,30,31,31,31,32,31,32,32,32,32,32,31,27,27,50,51,50,50,51,50,50,50,50,49,50,49,49,49,49,47,48,22,24,33,33,24,21,21,24,23,27,31,31,32,32,32,32,32,32,32,34,32,32,31,31,30,27,23,23,23,27,27,27,27,30,30,30,31,31,31,31,32,32,32,32,31,31,30,27,50,49,49,50,49,50,49,49,49,49,49,49,49,47,48,45,22,33,33,33,24,24,21,21,21,23,27,30,31,31,32,32,32,32,32,32,32,32,32,31,31,30,23,40,28,23,23,23,27,27,27,30,30,31,31,32,32,32,32,32,31,32,31,30,23,48,52,48,49,49,47,47,47,53,48,45,48,45,48,48,20,33,33,33,24,24,21,21,21,21,24,23,27,31,31,32,32,32,32,32,32,32,32,32,32,31,27,23,43,40,28,23,23,27,27,27,27,27,30,31,31,32,31,31,31,31,32,31,27,53,53,52,52,52,52,48,48,48,48,48,45,48,48,45,20,33,33,33,24,24,21,21,21,21,21,24,23,27,31,31,32,32,32,32,32,32,34,34,32,32,31,23,44,40,49,49,49,28,28,23,23,27,27,27,30,30,30,30,31,32,32,32,31,48,48,48,52,53,48,53,52,48,45,48,52,45,48,48,20,33,33,33,24,24,21,21,21,24,21,21,21,24,27,31,31,32,32,32,34,34,36,36,36,34,32,31,23,50,51,50,49,49,49,49,49,49,28,23,27,30,31,31,30,31,32,32,31,23,52,52,48,53,52,48,52,48,48,48,48,48,52,48,22,33,33,33,33,24,24,21,21,33,24,21,22,22,24,23,27,31,31,32,34,34,35,35,35,36,32,31,23,49,49,49,49,49,49,47,52,48,48,52,23,31,31,31,31,31,31,31,31,27,48,52,48,52,48,48,48,48,48,48,53,48,48,52]