├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bng.h ├── bng.html ├── bng.js ├── bng.wat.in ├── bng_test.c ├── bngviewer.c ├── png2bng.c ├── stb_image.h └── tsodinw.png /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | prime 3 | png2bng 4 | png2bng_test 5 | bng_test 6 | bngviewer 7 | *.wasm 8 | *.bng 9 | *.wat 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BNGVIEWER_CFLAGS=$(shell pkg-config --cflags sdl2) -ggdb 2 | BNGVIEWER_LIBS=$(shell pkg-config --libs sdl2) 3 | 4 | PNG2BNG_CFLAGS=-ggdb 5 | 6 | BNG_TEST_CFLAGS=-ggdb 7 | 8 | .PHONY: all 9 | all: png2bng tsodinw.bng bngviewer bng.wasm 10 | 11 | png2bng: png2bng.c bng.h stb_image.h 12 | $(CC) $(PNG2BNG_CFLAGS) -o png2bng png2bng.c -lm 13 | 14 | tsodinw.bng: png2bng tsodinw.png 15 | ./png2bng tsodinw.png tsodinw.bng GRAB 16 | 17 | bngviewer: bngviewer.c bng.h 18 | $(CC) $(BNGVIEWER_CFLAGS) -o bngviewer bngviewer.c $(BNGVIEWER_LIBS) 19 | 20 | bng.wasm: bng.wat 21 | wat2wasm bng.wat 22 | 23 | bng.wat: bng.wat.in 24 | cpp -P bng.wat.in > bng.wat 25 | 26 | .PHONY: test 27 | test: bng_test 28 | ./bng_test 29 | 30 | bng_test: bng_test.c bng.h stb_image.h 31 | $(CC) $(BNG_TEST_CFLAGS) -o bng_test bng_test.c -lm 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extending Native Browser Capabilities with WebAssembly 2 | 3 | ## Quick Start 4 | 5 | ```console 6 | $ make -B 7 | $ python -m SimpleHTTPServer 8080 8 | $ iexplorer.exe http://localhost:8080/bng.html 9 | ``` 10 | 11 | ## Research Notes 12 | 13 | - [x] Premise: Performance of WASM is probably comparable to native code 14 | - [x] Let's check it by implementing a Prime Number Crunching as a "Benchmark": 15 | - [x] https://github.com/tsoding/prime-benchmark 16 | - [ ] Idea: Let's imagine we invented a new image format that didn't exist before 17 | - [ ] If we implement the image support in WASM can we make it feel like a native browser support 18 | - [x] [Implement png2bng](./png2bng.c) 19 | - [x] [Implement bngviewer](./bngviewer.c) 20 | - [x] Implement bng support in WASM 21 | - [x] fetch("tsodinw.bng"): 22 | ```js 23 | fetch("./tsodinw.bng").then((x) => console.log(x.arrayBuffer())) 24 | ``` 25 | - [x] Put the fetched file into WASM memory 26 | ```js 27 | let bngFile = await fetch("tsodinw.bng"); 28 | let fileData = await bngFile.arrayBuffer(); 29 | let memory = new WebAssembly.Memory({initial: 10, maximum: 10}); 30 | new Uint8Array(memory.buffer).set(new Uint8Array(fileData)); 31 | ``` 32 | - [x] Call a WASM function that turns the file into Image Data 33 | - [x] Take out the Image Data from WASM memory and display it 34 | - [x] Add more interesting features to bng to test the support 35 | - [x] Different pixel formats 36 | - [x] Different compressions 37 | - [ ] Reusable BNG library that works both natively and being compiled to WASM 38 | - [ ] Check out this article: https://surma.dev/things/c-to-webassembly/ 39 | - [ ] img tag integration 40 | - [ ] Check out object URLs: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 41 | -------------------------------------------------------------------------------- /bng.h: -------------------------------------------------------------------------------- 1 | #ifndef BNG_H_ 2 | #define BNG_H_ 3 | 4 | #define BNG_MAGIC 0x21474E42 5 | 6 | #if defined(__GNUC__) || defined(__clang__) 7 | # define PACKED __attribute__((packed)) 8 | #else 9 | # warning "Packed attributes for struct is not implemented for this compiler. This may result in a program working incorrectly. Feel free to fix that and submit a Pull Request to https://github.com/tsoding/bng" 10 | # define PACKED 11 | #endif 12 | 13 | struct Bng_Pixel_Format 14 | { 15 | uint8_t red_byte; // 0 16 | uint8_t green_byte; // 1 17 | uint8_t blue_byte; // 2 18 | uint8_t alpha_byte; // 3 19 | } PACKED; 20 | 21 | int bng_pixel_format_equals(struct Bng_Pixel_Format a, struct Bng_Pixel_Format b) 22 | { 23 | return a.red_byte == b.red_byte && 24 | a.green_byte == b.green_byte && 25 | a.blue_byte == b.blue_byte && 26 | a.alpha_byte == b.alpha_byte; 27 | } 28 | 29 | const struct Bng_Pixel_Format RGBA = {0, 1, 2, 3}; 30 | const struct Bng_Pixel_Format GRBA = {1, 0, 2, 3}; 31 | const struct Bng_Pixel_Format BGRA = {2, 1, 0, 3}; 32 | const struct Bng_Pixel_Format GBRA = {1, 2, 0, 3}; 33 | const struct Bng_Pixel_Format BRGA = {2, 0, 1, 3}; 34 | const struct Bng_Pixel_Format RBGA = {0, 2, 1, 3}; 35 | const struct Bng_Pixel_Format ABGR = {3, 2, 1, 0}; 36 | const struct Bng_Pixel_Format BAGR = {2, 3, 1, 0}; 37 | const struct Bng_Pixel_Format BGAR = {2, 1, 3, 0}; 38 | const struct Bng_Pixel_Format AGBR = {3, 1, 2, 0}; 39 | const struct Bng_Pixel_Format GABR = {1, 3, 2, 0}; 40 | const struct Bng_Pixel_Format GBAR = {1, 2, 3, 0}; 41 | const struct Bng_Pixel_Format ARGB = {3, 0, 1, 2}; 42 | const struct Bng_Pixel_Format RAGB = {0, 3, 1, 2}; 43 | const struct Bng_Pixel_Format RGAB = {0, 1, 3, 2}; 44 | const struct Bng_Pixel_Format AGRB = {3, 1, 0, 2}; 45 | const struct Bng_Pixel_Format GARB = {1, 3, 0, 2}; 46 | const struct Bng_Pixel_Format GRAB = {1, 0, 3, 2}; 47 | const struct Bng_Pixel_Format ARBG = {3, 0, 2, 1}; 48 | const struct Bng_Pixel_Format RABG = {0, 3, 2, 1}; 49 | const struct Bng_Pixel_Format RBAG = {0, 2, 3, 1}; 50 | const struct Bng_Pixel_Format ABRG = {3, 2, 0, 1}; 51 | const struct Bng_Pixel_Format BARG = {2, 3, 0, 1}; 52 | const struct Bng_Pixel_Format BRAG = {2, 0, 3, 1}; 53 | 54 | struct Bng_Pixel_Format_Name 55 | { 56 | char cstr[5]; 57 | }; 58 | 59 | struct Bng_Pixel_Format_Name name_of_pixel_format(struct Bng_Pixel_Format format) 60 | { 61 | struct Bng_Pixel_Format_Name name = {0}; 62 | // BTW: if sizeof(Bng_Pixel_Format) was 1 I would not have to do these asserts, because then 63 | // the values of byte indices could not be physically equal or greater than 4. 64 | assert(format.red_byte < 4); 65 | name.cstr[format.red_byte] = 'R'; 66 | assert(format.green_byte < 4); 67 | name.cstr[format.green_byte] = 'G'; 68 | assert(format.blue_byte < 4); 69 | name.cstr[format.blue_byte] = 'B'; 70 | assert(format.alpha_byte < 4); 71 | name.cstr[format.alpha_byte] = 'A'; 72 | return name; 73 | } 74 | 75 | struct Bng_Pixel_Format pixel_format_by_name(const char *name) 76 | { 77 | struct Bng_Pixel_Format result = {0}; 78 | size_t n = strlen(name); 79 | if (n > 4) n = 4; 80 | 81 | for (size_t i = 0; i < n; ++i) { 82 | switch (name[i]) { 83 | case 'R': 84 | result.red_byte = i; 85 | break; 86 | case 'G': 87 | result.green_byte = i; 88 | break; 89 | case 'B': 90 | result.blue_byte = i; 91 | break; 92 | case 'A': 93 | result.alpha_byte = i; 94 | break; 95 | default: {} 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | 102 | struct RLE_Pair 103 | { 104 | uint32_t count; 105 | uint32_t pixel; 106 | }; 107 | 108 | struct Bng 109 | { 110 | uint32_t magic; 111 | uint32_t width; 112 | uint32_t height; 113 | struct Bng_Pixel_Format pixel_format; 114 | uint32_t pairs_count; 115 | struct RLE_Pair pairs[]; 116 | } PACKED; 117 | 118 | // NOTE: You have to allocate `width * height` amount of pixels 119 | void decompress_pixels(struct RLE_Pair *pairs, uint32_t pairs_count, uint32_t *pixels) 120 | { 121 | size_t pixels_count = 0; 122 | for (uint32_t i = 0; i < pairs_count; ++i) { 123 | for (uint32_t j = 0; j < pairs[i].count; ++j) { 124 | pixels[pixels_count++] = pairs[i].pixel; 125 | } 126 | } 127 | } 128 | 129 | // NOTE: You have to allocate `width * height` amount of pairs 130 | uint32_t compress_pixels(uint32_t width, uint32_t height, uint32_t *pixels, struct RLE_Pair *pairs) 131 | { 132 | uint32_t pairs_count = 0; 133 | const uint32_t n = width * height; 134 | 135 | for (uint32_t i = 0; i < n; ++i) { 136 | if (pairs_count > 0 && pairs[pairs_count - 1].pixel == pixels[i]) { 137 | pairs[pairs_count - 1].count += 1; 138 | } else { 139 | pairs_count += 1; 140 | pairs[pairs_count - 1].count = 1; 141 | pairs[pairs_count - 1].pixel = pixels[i]; 142 | } 143 | } 144 | 145 | return pairs_count; 146 | } 147 | 148 | #define GET_BYTE(bytes, index) ((bytes & (0xFF << ((4 - index - 1) * 8))) >> ((4 - index - 1) * 8)) 149 | #define SET_BYTE(bytes, index, byte) bytes = (bytes | (byte << ((4 - index - 1) * 8))) 150 | 151 | uint32_t convert_pixel(uint32_t pixel, 152 | struct Bng_Pixel_Format source, 153 | struct Bng_Pixel_Format desired) 154 | { 155 | uint32_t result = 0; 156 | SET_BYTE(result, desired.red_byte, GET_BYTE(pixel, source.red_byte)); 157 | SET_BYTE(result, desired.green_byte, GET_BYTE(pixel, source.green_byte)); 158 | SET_BYTE(result, desired.blue_byte, GET_BYTE(pixel, source.blue_byte)); 159 | SET_BYTE(result, desired.alpha_byte, GET_BYTE(pixel, source.alpha_byte)); 160 | return result; 161 | } 162 | 163 | #endif // BNG_H_ 164 | -------------------------------------------------------------------------------- /bng.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The First Website that support BNG natively Pog 5 | 6 | 7 |

Unprocessed:

8 |

The image before converting the BNG's pixel format into RGBA.

9 | 10 |

Processed:

11 |

The image after converting the BNG's pixel format into RGBA.

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bng.js: -------------------------------------------------------------------------------- 1 | function setImageDataToCanvas(imageData, canvasId) 2 | { 3 | let canvas = document.getElementById(canvasId); 4 | canvas.width = imageData.width; 5 | canvas.height = imageData.height; 6 | let context = canvas.getContext("2d"); 7 | context.putImageData(imageData, 0, 0); 8 | } 9 | 10 | async function bng(imageFileName) { 11 | // TODO(#4): keep bng.wasm as a byte array of bng.js 12 | let bngFile = await fetch(imageFileName); 13 | let fileData = await bngFile.arrayBuffer(); 14 | // TODO(#5): allocate WebAssembly memory based on the file size 15 | let memory = new WebAssembly.Memory({initial: 300, maximum: 1000}); 16 | new Uint8Array(memory.buffer).set(new Uint8Array(fileData)); 17 | let bngProgram = await WebAssembly.instantiateStreaming( 18 | fetch("bng.wasm"), 19 | { js: { mem: memory, print: arg => console.log(arg) } }); 20 | let offset = bngProgram.instance.exports.bng_offset(); 21 | let size = bngProgram.instance.exports.bng_size(); 22 | let width = bngProgram.instance.exports.bng_width(); 23 | let height = bngProgram.instance.exports.bng_height(); 24 | 25 | let unprocessedImageData = new ImageData( 26 | new Uint8ClampedArray(memory.buffer).slice(offset, offset + size), width, height); 27 | setImageDataToCanvas(unprocessedImageData, "bng-unprocessed"); 28 | 29 | bngProgram.instance.exports.bng_process(); 30 | let processedImageData = new ImageData( 31 | new Uint8ClampedArray(memory.buffer).slice(offset, offset + size), width, height); 32 | setImageDataToCanvas(processedImageData, "bng-processed"); 33 | } 34 | 35 | bng("tsodinw.bng").catch((e) => console.log(e)); 36 | -------------------------------------------------------------------------------- /bng.wat.in: -------------------------------------------------------------------------------- 1 | #define WIDTH_OFFSET 4 2 | #define HEIGHT_OFFSET 8 3 | #define PIXEL_FORMAT_OFFSET 12 4 | #define PAIRS_COUNT_OFFSET 16 5 | #define PAIRS_OFFSET 20 6 | 7 | #define PIXEL_SIZE 4 8 | #define PAIR_SIZE 8 9 | 10 | (module 11 | (memory (import "js" "mem") 1) 12 | (func $print (import "js" "print") (param i32)) 13 | (func $byte_offset 14 | (param $index i32) 15 | (result i32) 16 | (i32.mul 17 | (i32.sub 18 | (i32.sub (i32.const 4) (get_local $index)) 19 | (i32.const 1)) 20 | (i32.const 8))) 21 | (func $get_byte 22 | (param $pixel i32) 23 | (param $index i32) 24 | (result i32) 25 | (i32.shr_u 26 | (i32.and 27 | (get_local $pixel) 28 | (i32.shl (i32.const 255) (call $byte_offset (get_local $index)))) 29 | (call $byte_offset (get_local $index)))) 30 | (func $set_byte 31 | (param $pixel i32) 32 | (param $index i32) 33 | (param $byte i32) 34 | (result i32) 35 | (i32.or 36 | (get_local $pixel) 37 | (i32.shl (get_local $byte) (call $byte_offset (get_local $index))))) 38 | (func $convert_pixel 39 | (param $pixel i32) 40 | (result i32) 41 | (local $result i32) 42 | (set_local $result (i32.const 0)) 43 | (set_local $result 44 | (call $set_byte (get_local $result) 45 | (i32.const 0) 46 | (call $get_byte (get_local $pixel) 47 | (i32.load8_u (i32.const PIXEL_FORMAT_OFFSET))))) 48 | (set_local $result 49 | (call $set_byte (get_local $result) 50 | (i32.const 1) 51 | (call $get_byte (get_local $pixel) 52 | (i32.load8_u (i32.add (i32.const PIXEL_FORMAT_OFFSET) 53 | (i32.const 1)))))) 54 | (set_local $result 55 | (call $set_byte (get_local $result) 56 | (i32.const 2) 57 | (call $get_byte (get_local $pixel) 58 | (i32.load8_u (i32.add (i32.const PIXEL_FORMAT_OFFSET) 59 | (i32.const 2)))))) 60 | (set_local $result 61 | (call $set_byte (get_local $result) 62 | (i32.const 3) 63 | (call $get_byte (get_local $pixel) 64 | (i32.load8_u (i32.add (i32.const PIXEL_FORMAT_OFFSET) 65 | (i32.const 3)))))) 66 | (get_local $result)) 67 | (func $decompress_pixels 68 | (param $pairs_ptr i32) 69 | (param $pairs_count i32) 70 | (param $pixels_ptr i32) 71 | (block 72 | $exit 73 | (loop 74 | $repeat 75 | (br_if $exit (i32.eqz (get_local $pairs_count))) 76 | 77 | (block 78 | $exit_inner 79 | (loop 80 | $repeat_inner 81 | (br_if $exit_inner (i32.eqz (i32.load (get_local $pairs_ptr)))) 82 | 83 | (i32.store (get_local $pixels_ptr) 84 | (i32.load (i32.add (get_local $pairs_ptr) (i32.const PIXEL_SIZE)))) 85 | (set_local $pixels_ptr (i32.add (get_local $pixels_ptr) (i32.const PIXEL_SIZE))) 86 | 87 | (i32.store (get_local $pairs_ptr) 88 | (i32.sub (i32.load (get_local $pairs_ptr)) (i32.const 1))) 89 | (br $repeat_inner))) 90 | (set_local $pairs_count (i32.sub (get_local $pairs_count) (i32.const 1))) 91 | (set_local $pairs_ptr (i32.add (get_local $pairs_ptr) (i32.const PAIR_SIZE))) 92 | (br $repeat)))) 93 | (func (export "bng_process") 94 | (local $index i32) 95 | (local $count i32) 96 | (local $address i32) 97 | (call $decompress_pixels 98 | (i32.const PAIRS_OFFSET) 99 | (i32.load (i32.const PAIRS_COUNT_OFFSET)) 100 | (call $bng_offset)) 101 | (set_local $count (i32.mul (call $bng_width) (call $bng_height))) 102 | (block 103 | $exit 104 | (loop 105 | $repeat 106 | (br_if $exit (i32.ge_s 107 | (get_local $index) 108 | (get_local $count))) 109 | (set_local $address 110 | (i32.add 111 | (call $bng_offset) 112 | (i32.mul 113 | (get_local $index) 114 | (i32.const PIXEL_SIZE)))) 115 | (i32.store (get_local $address) (call $convert_pixel (i32.load (get_local $address)))) 116 | (set_local $index (i32.add (get_local $index) (i32.const 1))) 117 | (br $repeat)))) 118 | (func $bng_offset (export "bng_offset") 119 | (result i32) 120 | (i32.add (i32.const PAIRS_OFFSET) 121 | (i32.mul (i32.load (i32.const PAIRS_COUNT_OFFSET)) 122 | (i32.const PAIR_SIZE)))) 123 | (func $bng_width (export "bng_width") 124 | (result i32) 125 | (i32.load (i32.const WIDTH_OFFSET))) 126 | (func $bng_height (export "bng_height") 127 | (result i32) 128 | (i32.load (i32.const HEIGHT_OFFSET))) 129 | (func (export "bng_size") 130 | (result i32) 131 | (i32.mul 132 | (i32.mul 133 | (call $bng_width) 134 | (call $bng_height)) 135 | (i32.const PIXEL_SIZE)))) 136 | -------------------------------------------------------------------------------- /bng_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "bng.h" 7 | 8 | void test_convert_pixel() 9 | { 10 | #define TEST_CASE(input, source, desired, expected) \ 11 | do { \ 12 | uint32_t actual = convert_pixel(input, source, desired); \ 13 | if (actual != expected) { \ 14 | printf("%s:%d: Oopsie-doopsie!\n", __FILE__, __LINE__); \ 15 | printf("Actual: %08X\n", actual); \ 16 | printf("Expected: %08X\n", expected); \ 17 | exit(1); \ 18 | } \ 19 | printf("%08X == %08X, OK\n", actual, expected); \ 20 | } while (0) 21 | 22 | TEST_CASE(0xAABBCCDD, RGBA, ABGR, 0xDDCCBBAA); 23 | TEST_CASE(0xAABBCCDD, ABGR, GRAB, 0xCCDDAABB); 24 | TEST_CASE(0xAABBCCDD, RGBA, GRAB, 0xBBAADDCC); 25 | #undef TEST_CASE 26 | } 27 | 28 | void test_pixel_format_by_name() 29 | { 30 | #define TEST_CASE(input, expected)\ 31 | do {\ 32 | const struct Bng_Pixel_Format actual = pixel_format_by_name(input);\ 33 | if (!bng_pixel_format_equals(expected, actual)) {\ 34 | fprintf(stderr, "%s:%d: Oopsie-doopsie!\n", __FILE__, __LINE__);\ 35 | fprintf(stderr, "Actual: %s\n", name_of_pixel_format(actual));\ 36 | fprintf(stderr, "Expected: %s\n", name_of_pixel_format(expected));\ 37 | exit(1);\ 38 | }\ 39 | printf("%s == %s, OK\n", name_of_pixel_format(actual).cstr, name_of_pixel_format(expected).cstr);\ 40 | } while(0); 41 | 42 | TEST_CASE("RGBA", RGBA); 43 | TEST_CASE("ABGR", ABGR); 44 | TEST_CASE("GRAB", GRAB); 45 | #undef TEST_CASE 46 | } 47 | 48 | int main(int argc, char *argv[]) 49 | { 50 | test_convert_pixel(); 51 | test_pixel_format_by_name(); 52 | return 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /bngviewer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bng.h" 4 | 5 | void* read_whole_file(const char *filename) 6 | { 7 | FILE *f = fopen(filename, "rb"); 8 | 9 | fseek(f, 0, SEEK_END); 10 | long size = ftell(f); 11 | fseek(f, 0, SEEK_SET); 12 | void *data = malloc(size); 13 | size_t read_size = fread(data, 1, size, f); 14 | 15 | return data; 16 | } 17 | 18 | uint32_t *bng_load(const char *filepath, uint32_t *width, uint32_t *height) 19 | { 20 | struct Bng *compressed_bng = read_whole_file(filepath); 21 | 22 | uint32_t *pixels = malloc(compressed_bng->width * compressed_bng->height * sizeof(uint32_t)); 23 | decompress_pixels(compressed_bng->pairs, compressed_bng->pairs_count, pixels); 24 | 25 | for (size_t i = 0; i < compressed_bng->width * compressed_bng->height; ++i) { 26 | pixels[i] = convert_pixel(pixels[i], compressed_bng->pixel_format, RGBA); 27 | } 28 | 29 | *width = compressed_bng->width; 30 | *height = compressed_bng->height; 31 | 32 | return pixels; 33 | } 34 | 35 | int main(int argc, char *argv[]) 36 | { 37 | if (argc < 2) { 38 | fprintf(stderr, "./bngviewer "); 39 | exit(1); 40 | } 41 | 42 | const char *input_filepath = argv[1]; 43 | 44 | SDL_Init(SDL_INIT_VIDEO); 45 | 46 | SDL_Window *window = 47 | SDL_CreateWindow( 48 | "BNG Viewer", 49 | 0, 0, 800, 600, 50 | SDL_WINDOW_RESIZABLE); 51 | 52 | 53 | SDL_Renderer *renderer = 54 | SDL_CreateRenderer( 55 | window, -1, 56 | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); 57 | 58 | uint32_t width, height; 59 | uint32_t *pixels = bng_load(input_filepath, &width, &height); 60 | 61 | SDL_Surface* image_surface = 62 | SDL_CreateRGBSurfaceFrom(pixels, 63 | width, 64 | height, 65 | 32, 66 | width * 4, 67 | 0x000000FF, 68 | 0x0000FF00, 69 | 0x00FF0000, 70 | 0xFF000000); 71 | SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, image_surface); 72 | 73 | int quit = 0; 74 | while (!quit) { 75 | SDL_Event event; 76 | while (SDL_PollEvent(&event)) { 77 | switch (event.type) { 78 | case SDL_QUIT: { 79 | quit = 1; 80 | } break; 81 | } 82 | } 83 | 84 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); 85 | SDL_RenderClear(renderer); 86 | 87 | SDL_Rect rect = {0, 0, width, height}; 88 | 89 | SDL_RenderCopy(renderer, texture, &rect, &rect); 90 | SDL_RenderPresent(renderer); 91 | } 92 | 93 | SDL_Quit(); 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /png2bng.c: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "./stb_image.h" 3 | #include "./bng.h" 4 | 5 | int main(int argc, char **argv) 6 | { 7 | if (argc < 4) { 8 | fprintf(stderr, "png2bng \n"); 9 | exit(1); 10 | } 11 | 12 | const char *input_filepath = argv[1]; 13 | const char *output_filepath = argv[2]; 14 | struct Bng_Pixel_Format desired_format = pixel_format_by_name(argv[3]); 15 | 16 | int x, y, n; 17 | uint32_t *data = (uint32_t*)stbi_load(input_filepath, &x, &y, &n, 4); 18 | assert(data != NULL); 19 | 20 | struct Bng bng = { 21 | .magic = BNG_MAGIC, 22 | .width = x, 23 | .height = y, 24 | .pixel_format = desired_format, 25 | }; 26 | 27 | for (size_t i = 0; i < bng.width * bng.height; ++i) { 28 | data[i] = convert_pixel(data[i], RGBA, desired_format); 29 | } 30 | 31 | struct RLE_Pair *pairs = malloc(sizeof(struct RLE_Pair) * bng.width * bng.height); 32 | bng.pairs_count = compress_pixels(bng.width, bng.height, data, pairs); 33 | 34 | FILE *out = fopen(output_filepath, "wb"); 35 | fwrite(&bng, 1, sizeof(bng), out); 36 | fwrite(pairs, 1, bng.pairs_count * sizeof(struct RLE_Pair), out); 37 | fclose(out); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /tsodinw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/bng/193302ab19980afc50098affca49596f35603d32/tsodinw.png --------------------------------------------------------------------------------