├── .gitignore ├── examples ├── reddot.png ├── v-logo.png ├── v-logo-small.png ├── redline.v └── png-printer.v ├── v.mod ├── utils.v ├── invert.v ├── mirror.v ├── vpng.v ├── .github └── workflows │ └── ci.yml ├── crc.v ├── LICENSE ├── types.v ├── write.v ├── README.md ├── parse.v └── rotate.v /.gitignore: -------------------------------------------------------------------------------- 1 | examples/png-printer 2 | examples/redline 3 | vpng -------------------------------------------------------------------------------- /examples/reddot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henrixounez/vpng/HEAD/examples/reddot.png -------------------------------------------------------------------------------- /examples/v-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henrixounez/vpng/HEAD/examples/v-logo.png -------------------------------------------------------------------------------- /examples/v-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henrixounez/vpng/HEAD/examples/v-logo-small.png -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'vpng' 3 | description: 'PNG Image Processing Library' 4 | version: '0.0.1' 5 | dependencies: [] 6 | } -------------------------------------------------------------------------------- /utils.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | fn byte_to_int(bytes []u8) int { 4 | mut res := 0 5 | for i in 0 .. bytes.len { 6 | res += bytes[bytes.len - (i + 1)] << (i * 8) 7 | } 8 | return res 9 | } 10 | 11 | fn int_to_bytes(nb int) []u8 { 12 | return [u8((nb >> 24) & 0xFF), u8((nb >> 16) & 0xFF), u8((nb >> 8) & 0xFF), u8(nb & 0xFF)] 13 | } 14 | -------------------------------------------------------------------------------- /invert.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | fn invert_(mut png PngFile) { 4 | for i in 0 .. (png.pixels.len) { 5 | pix := png.pixels[i] 6 | match pix { 7 | TrueColor { 8 | png.pixels[i] = TrueColor{ 9 | red: 255 - pix.red 10 | green: 255 - pix.green 11 | blue: 255 - pix.blue 12 | } 13 | } 14 | TrueColorAlpha { 15 | png.pixels[i] = TrueColorAlpha{ 16 | red: 255 - pix.red 17 | green: 255 - pix.green 18 | blue: 255 - pix.blue 19 | alpha: pix.alpha 20 | } 21 | } 22 | else {} 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mirror.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | fn mirror_vertical_(mut png PngFile) { 4 | mut output := []Pixel{} 5 | 6 | for y in 0 .. (png.height) { 7 | for x in 0 .. (png.width) { 8 | output << png.pixels[y * png.width + (png.width - 1 - x)] 9 | } 10 | } 11 | png.pixels = output 12 | } 13 | 14 | fn mirror_horizontal_(mut png PngFile) { 15 | mut output := []Pixel{} 16 | 17 | for y in 0 .. (png.height) { 18 | for x in 0 .. (png.width) { 19 | output << png.pixels[((png.width - 1) - y) * png.width + x] 20 | } 21 | } 22 | png.pixels = output 23 | } 24 | -------------------------------------------------------------------------------- /vpng.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | const png_signature = [u8(0x89), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] 4 | 5 | pub fn read(filename string) !PngFile { 6 | return parse_(filename) 7 | } 8 | 9 | pub fn (png PngFile) write(filename string) { 10 | write_(png, filename) 11 | } 12 | 13 | pub fn (mut png PngFile) rotate(degree f64) { 14 | rotate_(mut png, degree) 15 | } 16 | 17 | pub fn (mut png PngFile) mirror_vertical() { 18 | mirror_vertical_(mut png) 19 | } 20 | 21 | pub fn (mut png PngFile) mirror_horizontal() { 22 | mirror_horizontal_(mut png) 23 | } 24 | 25 | pub fn (mut png PngFile) invert() { 26 | invert_(mut png) 27 | } 28 | -------------------------------------------------------------------------------- /examples/redline.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import vpng 4 | import os 5 | 6 | // RedLine 7 | // Adds a diagonal red line on a picture and saves it to an output file 8 | 9 | fn main() { 10 | if os.args.len != 3 { 11 | println('Missing filename') 12 | println('./redline input.png output.png') 13 | exit(1) 14 | } 15 | mut png := vpng.read(os.args[1])! 16 | mut min := png.width 17 | if png.height < png.width { 18 | min = png.height 19 | } 20 | for i in 0 .. min { 21 | pos := i * png.width + i 22 | match png.pixel_type { 23 | .truecolor { 24 | png.pixels[pos] = vpng.TrueColor{255, 0, 0} 25 | } 26 | .truecoloralpha { 27 | png.pixels[pos] = vpng.TrueColorAlpha{255, 0, 0, 255} 28 | } 29 | else {} 30 | } 31 | } 32 | png.write(os.args[2]) 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-latest: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Latest V 10 | uses: actions/checkout@v4 11 | with: 12 | repository: vlang/v 13 | path: v 14 | - name: Build V 15 | run: cd v && make && ./v symlink 16 | 17 | - name: Checkout repo 18 | uses: actions/checkout@v4 19 | - name: Symlink to ~/.vmodules/vpng 20 | run: ln -s $(realpath .) ~/.vmodules/vpng 21 | 22 | - name: Ensure code is formatted 23 | run: v fmt -verify . 24 | 25 | - name: Run tests 26 | run: v test . 27 | 28 | - name: Ensure examples compile 29 | run: v should-compile-all examples/ 30 | 31 | - name: Print V logo 32 | run: v run examples/png-printer.v examples/v-logo-small.png 33 | -------------------------------------------------------------------------------- /crc.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | // Implementation of https://www.w3.org/TR/PNG-CRCAppendix.html 4 | 5 | pub struct CRC { 6 | mut: 7 | crc_table []u64 = []u64{len: 256} 8 | crc_table_computed int 9 | } 10 | 11 | fn (mut cs CRC) make_crc_table() { 12 | mut c := u64(0) 13 | mut n := int(0) 14 | mut k := int(0) 15 | 16 | for n = 0; n < 256; n++ { 17 | c = u64(n) 18 | for k = 0; k < 8; k++ { 19 | if c & 1 != 0 { 20 | c = u64(i64(0xedb88320) ^ i64(c >> 1)) 21 | } else { 22 | c = c >> 1 23 | } 24 | } 25 | cs.crc_table[n] = c 26 | } 27 | cs.crc_table_computed = 1 28 | } 29 | 30 | fn (mut cs CRC) update_crc(crc u64, buf []u8, len int) u64 { 31 | mut c := u64(crc) 32 | mut n := int(0) 33 | 34 | if cs.crc_table_computed == 0 { 35 | cs.make_crc_table() 36 | } 37 | for n = 0; n < len; n++ { 38 | c = cs.crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8) 39 | } 40 | return c 41 | } 42 | 43 | pub fn (mut cs CRC) crc(buf []u8, len int) u64 { 44 | return cs.update_crc(u64(0xffffffff), buf, len) ^ u64(0xffffffff) 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Henrixounez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /types.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | pub enum PixelType { 4 | indexed 5 | grayscale 6 | grayscalealpha 7 | truecolor 8 | truecoloralpha 9 | } 10 | 11 | pub type Pixel = Grayscale | GrayscaleAlpha | Indexed | TrueColor | TrueColorAlpha 12 | 13 | pub struct PngFile { 14 | ihdr IHDR 15 | pub: 16 | width int 17 | height int 18 | pixel_type PixelType 19 | pub mut: 20 | palette []TrueColor 21 | pixels []Pixel 22 | } 23 | 24 | struct InternalPngFile { 25 | mut: 26 | ihdr IHDR 27 | stride int 28 | channels u8 29 | idat_chunks []u8 30 | raw_bytes []u8 31 | unfiltered_bytes []u8 32 | plte []u8 33 | pixels []Pixel 34 | pixel_type PixelType 35 | } 36 | 37 | pub struct IHDR { 38 | mut: 39 | width int 40 | height int 41 | bit_depth u8 42 | color_type u8 43 | compression_method u8 44 | filter_method u8 45 | interlace_method u8 46 | } 47 | 48 | pub struct Indexed { 49 | pub mut: 50 | index u8 51 | } 52 | 53 | pub struct Grayscale { 54 | pub mut: 55 | gray u8 56 | } 57 | 58 | pub struct GrayscaleAlpha { 59 | pub mut: 60 | gray u8 61 | alpha u8 62 | } 63 | 64 | pub struct TrueColor { 65 | pub mut: 66 | red u8 67 | green u8 68 | blue u8 69 | } 70 | 71 | pub struct TrueColorAlpha { 72 | pub mut: 73 | red u8 74 | green u8 75 | blue u8 76 | alpha u8 77 | } 78 | -------------------------------------------------------------------------------- /examples/png-printer.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import vpng 4 | import os 5 | import term 6 | 7 | // Png Printer 8 | // Read and print a png file on the terminal 9 | 10 | fn pixel_print_hd(x int, y int, png vpng.PngFile) { 11 | if x >= png.width || y >= png.height { 12 | return 13 | } 14 | top_pixel := png.pixels[y * png.width + x] 15 | mut top_str := '' 16 | match top_pixel { 17 | vpng.TrueColor { 18 | top_str = term.rgb(top_pixel.red, top_pixel.green, top_pixel.blue, '▀') 19 | } 20 | vpng.TrueColorAlpha { 21 | top_str = term.rgb(top_pixel.red, top_pixel.green, top_pixel.blue, '▀') 22 | } 23 | else { 24 | '' 25 | } 26 | } 27 | if y + 1 >= png.height { 28 | print(top_str) 29 | return 30 | } 31 | bot_pixel := png.pixels[(y + 1) * png.width + x] 32 | mut bot_str := '' 33 | match bot_pixel { 34 | vpng.TrueColor { 35 | bot_str = term.bg_rgb(bot_pixel.red, bot_pixel.green, bot_pixel.blue, top_str) 36 | } 37 | vpng.TrueColorAlpha { 38 | bot_str = term.bg_rgb(bot_pixel.red, bot_pixel.green, bot_pixel.blue, top_str) 39 | } 40 | else { 41 | '' 42 | } 43 | } 44 | print(bot_str) 45 | } 46 | 47 | fn hi_res_print(png vpng.PngFile) { 48 | mut x := 0 49 | mut y := 0 50 | 51 | for y < png.height { 52 | x = 0 53 | for x < png.width { 54 | pixel_print_hd(x, y, png) 55 | x++ 56 | } 57 | println('') 58 | y += 2 59 | } 60 | } 61 | 62 | fn main() { 63 | if os.args.len != 2 { 64 | println('Missing filename') 65 | exit(1) 66 | } 67 | filename := os.args[1] 68 | png := vpng.read(filename)! 69 | hi_res_print(png) 70 | } 71 | -------------------------------------------------------------------------------- /write.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | import os 4 | import compress.zlib 5 | 6 | fn write_(png PngFile, filename string) { 7 | mut file_bytes := []u8{} 8 | signature(mut file_bytes) 9 | write_chunks(mut file_bytes, png) 10 | os.write_file(filename, file_bytes.bytestr()) or { println('Error writing file ${err}') } 11 | } 12 | 13 | fn signature(mut file_bytes []u8) { 14 | file_bytes << png_signature 15 | } 16 | 17 | fn ihdr_chunk(mut file_bytes []u8, mut cs CRC, png PngFile) { 18 | mut ihdr_bytes := [u8(`I`), `H`, `D`, `R`] 19 | ihdr_bytes << int_to_bytes(png.width) // WIDTH 20 | ihdr_bytes << int_to_bytes(png.height) // HEIGHT 21 | ihdr_bytes << png.ihdr.bit_depth // BIT DEPTH 22 | ihdr_bytes << png.ihdr.color_type // COLOR TYPE 23 | ihdr_bytes << png.ihdr.compression_method // Compression Method 24 | ihdr_bytes << png.ihdr.filter_method // Filter Method 25 | ihdr_bytes << png.ihdr.interlace_method // Interlace Method 26 | len_bytes := int_to_bytes(ihdr_bytes.len - 4) 27 | crc_bytes := int_to_bytes(int(cs.crc(ihdr_bytes, ihdr_bytes.len))) 28 | file_bytes << len_bytes 29 | file_bytes << ihdr_bytes 30 | file_bytes << crc_bytes 31 | } 32 | 33 | fn iend_chunk(mut file_bytes []u8, mut cs CRC) { 34 | iend_bytes := [u8(`I`), `E`, `N`, `D`] 35 | file_bytes << int_to_bytes(0) 36 | file_bytes << iend_bytes 37 | file_bytes << int_to_bytes(int(cs.crc(iend_bytes, iend_bytes.len))) 38 | } 39 | 40 | fn idat_chunk(mut file_bytes []u8, mut cs CRC, png PngFile) { 41 | mut idat_bytes := []u8{} 42 | for y in 0 .. png.height { 43 | idat_bytes << 0 44 | for x in 0 .. png.width { 45 | pix := png.pixels[y * png.width + x] 46 | match pix { 47 | TrueColor { 48 | idat_bytes << pix.red 49 | idat_bytes << pix.green 50 | idat_bytes << pix.blue 51 | } 52 | TrueColorAlpha { 53 | idat_bytes << pix.red 54 | idat_bytes << pix.green 55 | idat_bytes << pix.blue 56 | idat_bytes << pix.alpha 57 | } 58 | Indexed { 59 | idat_bytes << pix.index 60 | } 61 | else {} 62 | } 63 | } 64 | } 65 | 66 | out := zlib.compress(idat_bytes) or { 67 | panic('failed to compress IDAT chunks') 68 | } 69 | mut out_bytes := [u8(`I`), `D`, `A`, `T`] 70 | out_bytes << out 71 | file_bytes << int_to_bytes(out_bytes.len - 4) 72 | file_bytes << out_bytes 73 | file_bytes << int_to_bytes(int(cs.crc(out_bytes, out_bytes.len))) 74 | } 75 | 76 | fn plte_chunk(mut file_bytes []u8, mut cs CRC, png PngFile) { 77 | if png.pixel_type != PixelType.indexed { 78 | return 79 | } 80 | mut out_bytes := [u8(`P`), `L`, `T`, `E`] 81 | for i in 0 .. png.palette.len { 82 | out_bytes << [png.palette[i].red] 83 | out_bytes << [png.palette[i].green] 84 | out_bytes << [png.palette[i].blue] 85 | } 86 | file_bytes << int_to_bytes(out_bytes.len - 4) 87 | file_bytes << out_bytes 88 | file_bytes << int_to_bytes(int(cs.crc(out_bytes, out_bytes.len))) 89 | } 90 | 91 | fn write_chunks(mut file_bytes []u8, png PngFile) { 92 | mut cs := CRC{} 93 | ihdr_chunk(mut file_bytes, mut cs, png) 94 | plte_chunk(mut file_bytes, mut cs, png) 95 | idat_chunk(mut file_bytes, mut cs, png) 96 | iend_chunk(mut file_bytes, mut cs) 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **V-PNG**: V PNG Image Processing 2 | 3 | ## How to use : 4 | 5 | Its **simple**, download the module and then you just have to do : 6 | ```v 7 | import vpng 8 | 9 | png_file := vpng.read("image.png") or { return } 10 | png_file.write("output.png") 11 | ``` 12 | 13 |
14 |
15 |
16 | 17 | ## Tips 18 | 19 |
20 | 21 | To deal with the different kinds of `Pixel`, (cf: [Pixel Types](###pixel-types)) the prefered method is : 22 | ```v 23 | pixel := png_file.pixels[i] 24 | match pixel { 25 | vpng.Grayscale { 26 | ... 27 | } 28 | vpng.TrueColor { 29 | ... 30 | } 31 | ... 32 | ... 33 | } 34 | ``` 35 | 36 |
37 |
38 |
39 | 40 | ## Methods 41 | | Method | use | 42 | |-|-| 43 | | `read(filename string) ?PngFile` | Parses the given `filename` png file and returns an optional `PngFile`. | 44 | | `(PngFile).write(filename string)` | Writes the given `PngFile` in the `filename` png file. 45 | 46 |
47 | 48 | ## Types 49 | 50 | ### Structure 51 | - `PngFile`, Object returned by the `.parse` function. Contains all useful information about the parsed png file : 52 | ```v 53 | pub struct PngFile { 54 | ihdr IHDR 55 | pub: 56 | width int // Width of image 57 | height int // Height of image 58 | pixel_type PixelType // Pixels type 59 | pub mut: 60 | pixels []Pixel // Modifiable pixel array 61 | } 62 | ``` 63 | 64 | ### Pixel Types 65 | - `PixelType`, Enum for type of pixel possible in the PngFile : 66 | ```v 67 | pub enum PixelType { 68 | indexed 69 | grayscale 70 | grayscalealpha 71 | truecolor 72 | truecoloralpha 73 | } 74 | ``` 75 | - `PixelType`, Generic type for the different possible types of pixels : 76 | ```v 77 | pub type Pixel = Grayscale | GrayscaleAlpha | Indexed | TrueColor | TrueColorAlpha 78 | ``` 79 | - `Pixel`s : 80 | - Indexed (*Not supported yet*) : 81 | ```v 82 | pub struct Indexed { 83 | pub mut: 84 | index byte 85 | } 86 | ``` 87 | - Grayscale (*Not supported yet*) : 88 | ```v 89 | pub struct Grayscale { 90 | pub mut: 91 | gray byte 92 | } 93 | ``` 94 | - GrayscaleAlpha (*Not supported yet*) : 95 | ```v 96 | pub struct GrayscaleAlpha { 97 | pub mut: 98 | gray byte 99 | alpha byte 100 | } 101 | ``` 102 | - TrueColor (RGB) : 103 | ```v 104 | pub struct TrueColor { 105 | pub mut: 106 | red byte 107 | green byte 108 | blue byte 109 | } 110 | ``` 111 | - TrueColorAlpha (RGBA) : 112 | ```v 113 | pub struct TrueColorAlpha { 114 | pub mut: 115 | red byte 116 | green byte 117 | blue byte 118 | alpha byte 119 | } 120 | ``` 121 | 122 |
123 |
124 |
125 | 126 | ## Examples: 127 | - `png-printer.v`: Give it a `.png` file and it will print it for you in the terminal. 128 | - `redline.v`: Give it an input `.png` file and an output `.png` file. It will read the input, add a diagonal red line and write the result on the output file. 129 | 130 | ### How to compile them from GitHub repository: 131 | ```bash 132 | [vpng]$ v examples/png-printer.v -path "..|@vlib|@vmodules" 133 | ``` 134 | 135 |
136 |
137 |
138 | 139 | ## Todo : 140 | - [ ] Handle all types of colors 141 | - [x] Indexed 142 | - [ ] Grayscale 143 | - [ ] GrayscaleAlpha 144 | - [x] TrueColor 145 | - [x] TrueColorAlpha 146 | - [ ] Functions to easily manipulate the pixels / image, for example : 147 | - [ ] Resize 148 | - [x] Mirror 149 | - [x] Rotate 150 | - [ ] Zoom 151 | - [ ] Slide 152 | - [ ] Crop 153 | - [x] Invert colors 154 | - [ ] Change colors 155 | - [ ] ... 156 | - [x] Write a png file (*Might be hard to have an optimized one*) 157 | - [ ] Optimize it 158 | - [ ] Filter pixels lines 159 | - [ ] Group IDAT in chunks 160 | - [ ] Save metadata 161 | 162 | -------------------------------------------------------------------------------- /parse.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | import os 4 | import compress.zlib 5 | 6 | fn parse_(filename string) !PngFile { 7 | file_bytes := os.read_bytes(filename) or { return err } 8 | read_signature(file_bytes[..8]) or { return err } 9 | mut png := read_chunks(file_bytes[8..]) 10 | png.channels = match png.ihdr.color_type { 11 | 3 { 1 } // Indexed 12 | 0 { 1 } // Grayscale 13 | 4 { 2 } // Grayscale Alpha 14 | 2 { 3 } // TrueColor 15 | 6 { 4 } // TrueColor Alpha 16 | else { 1 } // TODO ERROR 17 | } 18 | png.pixel_type = match png.ihdr.color_type { 19 | 3 { PixelType.indexed } // Indexed 20 | 0 { PixelType.grayscale } // Grayscale 21 | 4 { PixelType.grayscalealpha } // Grayscale Alpha 22 | 2 { PixelType.truecolor } // TrueColor 23 | 6 { PixelType.truecoloralpha } // TrueColor Alpha 24 | else { PixelType.grayscale } // TODO ERROR 25 | } 26 | png.raw_bytes = decompress_idat(png) 27 | png.pixels = read_bytes(mut png) 28 | mut plte := []TrueColor{} 29 | for i := 0; i < png.plte.len; i += 3 { 30 | plte << TrueColor{ 31 | red: png.plte[i] 32 | green: png.plte[i + 1] 33 | blue: png.plte[i + 2] 34 | } 35 | } 36 | return PngFile{ 37 | width: png.ihdr.width 38 | height: png.ihdr.height 39 | pixels: png.pixels 40 | pixel_type: png.pixel_type 41 | ihdr: png.ihdr 42 | palette: plte 43 | } 44 | } 45 | 46 | fn read_signature(signature []u8) !bool { 47 | is_good := signature == png_signature 48 | if !is_good { 49 | return error('Wrong PNG signature') 50 | } 51 | return true 52 | } 53 | 54 | fn read_ihdr(chunk_data []u8) IHDR { 55 | return IHDR{ 56 | width: byte_to_int(chunk_data[..4]) 57 | height: byte_to_int(chunk_data[4..8]) 58 | bit_depth: chunk_data[8] 59 | color_type: chunk_data[9] 60 | compression_method: chunk_data[10] 61 | filter_method: chunk_data[11] 62 | interlace_method: chunk_data[12] 63 | } 64 | } 65 | 66 | fn byte_a(r int, c int, png InternalPngFile) int { 67 | return if c >= png.channels { 68 | png.unfiltered_bytes[r * png.stride + c - png.channels] 69 | } else { 70 | 0 71 | } 72 | } 73 | 74 | fn byte_b(r int, c int, png InternalPngFile) int { 75 | return if r > 0 { 76 | png.unfiltered_bytes[(r - 1) * png.stride + c] 77 | } else { 78 | 0 79 | } 80 | } 81 | 82 | fn byte_c(r int, c int, png InternalPngFile) int { 83 | return if r > 0 && c >= png.channels { 84 | png.unfiltered_bytes[(r - 1) * png.stride + c - png.channels] 85 | } else { 86 | 0 87 | } 88 | } 89 | 90 | fn abs(val int) int { 91 | return if val < 0 { 92 | -val 93 | } else { 94 | val 95 | } 96 | } 97 | 98 | fn paeth(a int, b int, c int) int { 99 | p := a + b - c 100 | pa := abs(p - a) 101 | pb := abs(p - b) 102 | pc := abs(p - c) 103 | mut pr := 0 104 | pr = if pa <= pb && pa <= pc { 105 | a 106 | } else if pb <= pc { 107 | b 108 | } else { 109 | c 110 | } 111 | return pr 112 | } 113 | 114 | @[direct_array_access] 115 | fn read_bytes(mut png InternalPngFile) []Pixel { 116 | png.stride = png.ihdr.width * png.channels 117 | mut i := 0 118 | for r in 0 .. (png.ihdr.height) { 119 | filter_type := png.raw_bytes[i] 120 | i++ 121 | for c in 0 .. (png.stride) { 122 | filt := png.raw_bytes[i] 123 | i++ 124 | new_byte := match filter_type { 125 | 0 { filt } 126 | 1 { filt + byte_a(r, c, png) } 127 | 2 { filt + byte_b(r, c, png) } 128 | 3 { filt + (byte_a(r, c, png) + byte_b(r, c, png)) / 2 } 129 | else { filt + paeth(byte_a(r, c, png), byte_b(r, c, png), byte_c(r, c, png)) } 130 | } 131 | png.unfiltered_bytes << u8(new_byte & 0xff) 132 | } 133 | } 134 | mut res := []Pixel{} 135 | for index := 0; index < png.unfiltered_bytes.len; index += png.channels { 136 | match png.ihdr.color_type { 137 | 3 { // Indexed 138 | res << Indexed{ 139 | index: png.unfiltered_bytes[index] 140 | } 141 | } 142 | 0 { // Grayscale 143 | } 144 | 4 { // Grayscale Alpha 145 | } 146 | 2 { // TrueColor 147 | res << TrueColor{ 148 | red: png.unfiltered_bytes[index] 149 | green: png.unfiltered_bytes[index + 1] 150 | blue: png.unfiltered_bytes[index + 2] 151 | } 152 | } 153 | 6 { // TrueColor Alpha 154 | res << TrueColorAlpha{ 155 | red: png.unfiltered_bytes[index] 156 | green: png.unfiltered_bytes[index + 1] 157 | blue: png.unfiltered_bytes[index + 2] 158 | alpha: png.unfiltered_bytes[index + 3] 159 | } 160 | } 161 | else {} 162 | } 163 | } 164 | return res 165 | } 166 | 167 | fn read_chunks(file []u8) InternalPngFile { 168 | mut index := 0 169 | mut png := InternalPngFile{} 170 | for index < file.len { 171 | chunk_size := byte_to_int(file[index..index + 4]) 172 | index += 4 173 | name := file[index..index + 4].bytestr() 174 | if name == 'IEND' { 175 | break 176 | } 177 | index += 4 178 | chunk_data := file[index..index + chunk_size] 179 | match name { 180 | 'IEND' { 181 | break 182 | } 183 | 'IHDR' { 184 | png.ihdr = read_ihdr(chunk_data) 185 | } 186 | 'PLTE' { 187 | png.plte << chunk_data 188 | } 189 | 'IDAT' { 190 | png.idat_chunks << chunk_data 191 | } 192 | else { // println("Chunk $name not handled") 193 | } 194 | } 195 | index += chunk_size 196 | index += 4 197 | } 198 | return png 199 | } 200 | 201 | fn decompress_idat(png InternalPngFile) []u8 { 202 | return zlib.decompress(png.idat_chunks) or { 203 | panic('failed to decompress IDAT chunks') 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /rotate.v: -------------------------------------------------------------------------------- 1 | module vpng 2 | 3 | import math 4 | 5 | fn normalize_value(val u8) u8 { 6 | if val < 0 { 7 | return 0 8 | } else if val > 255 { 9 | return 255 10 | } else { 11 | return val 12 | } 13 | } 14 | 15 | fn rotate_(mut png PngFile, degree f64) { 16 | angle := degree * math.pi / 180 17 | 18 | i_center_x := png.width / 2 19 | i_center_y := png.height / 2 20 | 21 | mut output := []Pixel{} 22 | 23 | for i := 0; i < png.height; i++ { 24 | for j := 0; j < png.width; j++ { 25 | x := j - i_center_x 26 | y := i_center_y - i 27 | f_distance := math.sqrt(x * x + y * y) 28 | mut f_polar_angle := 0.0 29 | if x == 0 { 30 | if y == 0 { 31 | output << png.pixels[i * png.width + j] 32 | continue 33 | } else if y < 0 { 34 | f_polar_angle = 1.5 * math.pi 35 | } else { 36 | f_polar_angle = 0.5 * math.pi 37 | } 38 | } else { 39 | f_polar_angle = math.atan2(y, x) 40 | } 41 | f_polar_angle -= angle 42 | 43 | f_true_x := (f_distance * math.cos(f_polar_angle)) + i_center_x 44 | f_true_y := i_center_y - (f_distance * math.sin(f_polar_angle)) 45 | 46 | i_floor_x := math.floor(f_true_x) 47 | i_floor_y := math.floor(f_true_y) 48 | i_ceiling_x := math.ceil(f_true_x) 49 | i_ceiling_y := math.ceil(f_true_y) 50 | 51 | if i_floor_x < 0 || i_ceiling_x < 0 || i_floor_x >= png.width 52 | || i_ceiling_x >= png.width || i_floor_y < 0 || i_ceiling_y < 0 53 | || i_floor_y >= png.height || i_ceiling_y >= png.height { 54 | match png.pixel_type { 55 | .truecolor { 56 | output << TrueColor{ 57 | red: 0 58 | green: 0 59 | blue: 0 60 | } 61 | } 62 | .truecoloralpha { 63 | output << TrueColorAlpha{ 64 | red: 0 65 | green: 0 66 | blue: 0 67 | alpha: 0 68 | } 69 | } 70 | else {} 71 | } 72 | } else { 73 | f_delta_x := f_true_x - i_floor_x 74 | f_delta_y := f_true_y - i_floor_y 75 | 76 | match png.pixel_type { 77 | .truecolor { 78 | clr_top_left := png.pixels[int(i_floor_y * png.width + i_floor_x)] as TrueColor 79 | clr_top_right := png.pixels[int(i_floor_y * png.width + i_ceiling_x)] as TrueColor 80 | clr_bottom_left := png.pixels[int(i_ceiling_y * png.width + i_floor_x)] as TrueColor 81 | clr_bottom_right := png.pixels[int(i_ceiling_y * png.width + i_ceiling_x)] as TrueColor 82 | 83 | f_top := TrueColor{ 84 | red: u8((1 - f_delta_x) * clr_top_left.red + 85 | f_delta_x * clr_top_right.red) 86 | green: u8((1 - f_delta_x) * clr_top_left.green + 87 | f_delta_x * clr_top_right.green) 88 | blue: u8((1 - f_delta_x) * clr_top_left.blue + 89 | f_delta_x * clr_top_right.blue) 90 | } 91 | f_bottom := TrueColor{ 92 | red: u8((1 - f_delta_x) * clr_bottom_left.red + 93 | f_delta_x * clr_bottom_right.red) 94 | green: u8((1 - f_delta_x) * clr_bottom_left.green + 95 | f_delta_x * clr_bottom_right.green) 96 | blue: u8((1 - f_delta_x) * clr_bottom_left.blue + 97 | f_delta_x * clr_bottom_right.blue) 98 | } 99 | output << TrueColor{ 100 | red: normalize_value(u8((1 - f_delta_y) * f_top.red + 101 | f_delta_y * f_bottom.red)) 102 | green: normalize_value(u8((1 - f_delta_y) * f_top.green + 103 | f_delta_y * f_bottom.green)) 104 | blue: normalize_value(u8((1 - f_delta_y) * f_top.blue + 105 | f_delta_y * f_bottom.blue)) 106 | } 107 | } 108 | .truecoloralpha { 109 | clr_top_left := png.pixels[int(i_floor_y * png.width + i_floor_x)] as TrueColorAlpha 110 | clr_top_right := png.pixels[int(i_floor_y * png.width + i_ceiling_x)] as TrueColorAlpha 111 | clr_bottom_left := png.pixels[int(i_ceiling_y * png.width + i_floor_x)] as TrueColorAlpha 112 | clr_bottom_right := png.pixels[int(i_ceiling_y * png.width + i_ceiling_x)] as TrueColorAlpha 113 | 114 | f_top := TrueColorAlpha{ 115 | red: u8((1 - f_delta_x) * clr_top_left.red + 116 | f_delta_x * clr_top_right.red) 117 | green: u8((1 - f_delta_x) * clr_top_left.green + 118 | f_delta_x * clr_top_right.green) 119 | blue: u8((1 - f_delta_x) * clr_top_left.blue + 120 | f_delta_x * clr_top_right.blue) 121 | alpha: u8((1 - f_delta_x) * clr_top_left.alpha + 122 | f_delta_x * clr_top_right.alpha) 123 | } 124 | f_bottom := TrueColorAlpha{ 125 | red: u8((1 - f_delta_x) * clr_bottom_left.red + 126 | f_delta_x * clr_bottom_right.red) 127 | green: u8((1 - f_delta_x) * clr_bottom_left.green + 128 | f_delta_x * clr_bottom_right.green) 129 | blue: u8((1 - f_delta_x) * clr_bottom_left.blue + 130 | f_delta_x * clr_bottom_right.blue) 131 | alpha: u8((1 - f_delta_x) * clr_bottom_left.alpha + 132 | f_delta_x * clr_bottom_right.alpha) 133 | } 134 | output << TrueColorAlpha{ 135 | red: normalize_value(u8((1 - f_delta_y) * f_top.red + 136 | f_delta_y * f_bottom.red)) 137 | green: normalize_value(u8((1 - f_delta_y) * f_top.green + 138 | f_delta_y * f_bottom.green)) 139 | blue: normalize_value(u8((1 - f_delta_y) * f_top.blue + 140 | f_delta_y * f_bottom.blue)) 141 | alpha: normalize_value(u8((1 - f_delta_y) * f_top.alpha + 142 | f_delta_y * f_bottom.alpha)) 143 | } 144 | } 145 | else { 146 | TrueColor{ 147 | red: 0 148 | green: 0 149 | blue: 0 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | png.pixels = output 157 | } 158 | --------------------------------------------------------------------------------