├── .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 |