Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
J-Siu committed Aug 17, 2020
0 parents commit 9962189
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go-png2ico
tmp
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The MIT License

Copyright (c) 2020 John Siu

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## go-png2ico

<!-- TOC -->

- [What It Does](#what-it-does)
- [What It Does Not](#what-it-does-not)
- [Usage](#usage)
- [Repository](#repository)
- [Contributors](#contributors)
- [Change Log](#change-log)
- [License](#license)

<!-- /TOC -->

### What It Does

- Create ICO file from PNG files
- ICO use PNG format for storage
- Minimum overhead(16byte) per PNG added
- PNG header check for input files
- PNG header check for output file to avoid mistake

### What It Does Not

- Change PNG to BMP inside ICO
- Check file extension
- Transform PNG

### Usage

```sh
go-png2ico
```

```sh
go-png2ico MIT License Copyright (c) 2020 John Siu
Support: https://github.com/J-Siu/go-png2ico/issues
Usage: go-png2ico <PNG file> <PNG file> ... <ICO file>
Debug: export _DEBUG=true
```

### Repository

- [id3go](https://github.com/J-Siu/go-png2ico)

### Contributors

- [John Sing Dao Siu](https://github.com/J-Siu)

### Change Log

- 1.0
- Initial Commit

### License

The MIT License

Copyright (c) 2020 John Siu

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
296 changes: 296 additions & 0 deletions go-png2ico.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/*
The MIT License
Copyright (c) 2020 John Siu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package main

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"strconv"

"github.com/J-Siu/go-png2ico/helper"
)

// ICO structire
type ICO struct {
file string
fh *os.File
}

// PNG structure
type PNG struct {
file string // filename
fh *os.File
height uint8
width uint8
depth uint16 // bit/pixel
size uint32
offset uint32
isPNG bool
}

func log(msg ...interface{}) {
if helper.Debug {
fmt.Println("log:", msg)
}
}

func errCheck(e error) {
if e != nil {
fmt.Println(e)
os.Exit(1)
}
}

// Open : open PNG file
func (png *PNG) Open(file string) error {
log("PNG:Open:", file)

var e error
var fh *os.File
var n int
var n64 int64

fh, e = os.Open(file)
if e != nil {
return e
}

png.file = file
png.fh = fh
png.isPNG = false

/* PNG header check(25byte)
89 50 4e 47 0d 0a 1a 0a // 8byte - magic number
// IHDR chunk
xx xx xx xx // 4byte - chunk length
49 48 44 52 // 4byte - chunk type(IHDR)
xx xx xx xx // 4byte - width
xx xx xx xx // 4byte - height
xx // 1byte - bit death (bit/pixel)
*/
headerLen := 25
header := make([]byte, headerLen)
n, e = png.fh.Read(header)
if e != nil {
return e
}
log("PNG:Open:Header:", hex.EncodeToString(header), "(", n, ")")
n64, e = png.fh.Seek(0, 0)
if e != nil {
return e
}
log("PNG:Open:FH reset:", n64)

// 8byte header[0:8] - magic number
magic := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}
if bytes.Equal(magic[:], header[:8]) {
log("Found PNG magic.")
} else {
return errors.New("Not PNG")
}
// 4byte header[8:12] - chunk length - skipped
// 4byte header[12:16] - chunk type IHDR
chunk := []byte{0x49, 0x48, 0x44, 0x52}
if bytes.Equal(chunk[:], header[12:16]) {
log("Found IHDR chunk.")
} else {
return errors.New("PNG no IHDR chunk")
}

// It is PNG
png.isPNG = true

// 4byte header[16:20] - width
width := binary.BigEndian.Uint32(header[16:20])

// 4byte header[20:24] - height
height := binary.BigEndian.Uint32(header[20:24])

if width <= 256 && height <= 256 {
log("PNG:Open:", width, "x", height)
// ICO format use 0 for 256px
if width == 256 {
width = 0
}
if height == 256 {
height = 0
}
} else {
return errors.New(png.file + "(" + strconv.FormatUint(uint64(width), 10) + "x" + strconv.FormatUint(uint64(height), 10) + "): Width and height cannot be larger than 256.")
}
png.width = uint8(width)
png.height = uint8(height)

// 1byte header[25] - color depth
png.depth = uint16(uint8(header[24]))
log("PNG:Open:Bit/pixel:", png.depth)

stat, _ := os.Stat(file)
png.size = uint32(stat.Size())
log("PNG:Open:Size:", png.size)

// Pass all check, create PNG struct
log("PNG:Open:PNG:", *png)

return nil
}

// Read : read PNG file
func (png *PNG) Read() *[]byte {
log("PNG:READ:", png.file)

var e error
var n int

b := make([]byte, png.size)
n, e = png.fh.Read(b)
errCheck(e)

log("PNG:READ:byte", n)

return &b
}

// Open : open ICO filehandle
func (ico *ICO) Open(file string) {
var e error
log("ICO:Open:" + file)
ico.fh, e = os.Create(file)
errCheck(e)
}

// Write : write ICO
func (ico *ICO) Write(b *[]byte) {
var e error
var n int
n, e = ico.fh.Write(*b)
errCheck(e)
log("ICO:Write:byte:", n)
}

// ICONDIR - return ICONDIR byte array
func (ico *ICO) ICONDIR(num uint16) *[]byte {
/*
ICONDIR structure
00 00 // 2byte, must be 0
01 00 // 2byte, 1 for ICO
xx xx // 2byte, img number
*/
b0 := []byte{0, 0, 1, 0}
b4 := make([]byte, 2)
binary.LittleEndian.PutUint16(b4, num)
b := append(b0, b4...)
log("ICO:ICONDIR:", hex.EncodeToString(b))
return &b
}

// ICONDIRENTRY - return ICONDIRENTRY byte array
func (png *PNG) ICONDIRENTRY() *[]byte {
log("ICONDIRENTRY:", *png)
/*
ICONDIRENTRY structure
xx // 1byte, width
xx // 1byte, height
00 // 1byte, color palette number, 0 for PNG
00 // 1byte, reserved, 0
00 00 // 2byte, color planes, 0 for PNG
xx xx // 2byte, color depth
xx xx xx xx // 4byte, image size
xx xx xx xx // 4byte, image offset
*/
b0 := []byte{png.width, png.height, 0, 0, 0, 0}
b6 := make([]byte, 2)
binary.LittleEndian.PutUint16(b6, png.depth)
ba := make([]byte, 4)
binary.LittleEndian.PutUint32(ba, png.size)
be := make([]byte, 4)
binary.LittleEndian.PutUint32(be, png.offset)
b := append(b0, b6...)
c := append(b, ba...)
d := append(c, be...)
log("PNG:ICONDIRENTRY:", hex.EncodeToString(d))

return &d
}

func usage() {
fmt.Println("go-png2ico MIT License Copyright (c) 2020 John Siu")
fmt.Println("Support: https://github.com/J-Siu/go-png2ico/issues")
fmt.Println("Usage: go-png2ico <PNG file> <PNG file> ... <ICO file>")
fmt.Println("Debug: export _DEBUG=true")
}

func main() {
//Debug
if os.Getenv("_DEBUG") == "true" {
helper.Debug = true
}

// ARGs
args := os.Args[1:]
argc := len(args)
switch argc {
case 0:
usage()
os.Exit(0)
case 1:
log("Input/Output file missing.")
os.Exit(1)
}

var e error

fileout := args[argc-1]
// Make sure destination file is not PNG
png := new(PNG)
if png.Open(fileout) == nil || png.isPNG {
e = errors.New(png.file + " is a PNG file.")
} else {
e = nil
}
errCheck(e)

// Get and calculate all PNGs info
pngs := []*PNG{}
pngc := argc - 1
var pngTotalSize uint32 = 0
LenICONDIR := 6
LenICONDIRENTRY := 16
LenAllICONDIRENTRY := LenICONDIRENTRY * pngc
for i := 0; i < pngc; i++ {
png := new(PNG)
e = png.Open(args[i])
errCheck(e)
// offset = len(ICONDIR) + len(all ICONDIRENTRY) + len(all PNG before current one)
png.offset = uint32(LenICONDIR) + uint32(LenAllICONDIRENTRY) + pngTotalSize
pngs = append(pngs, png)
pngTotalSize += png.size
}

// Open ICON
ico := new(ICO)
ico.Open(fileout)
ico.Write(ico.ICONDIR(uint16(pngc)))
// Write ICONDIRENTRY
for i := 0; i < pngc; i++ {
ico.Write(pngs[i].ICONDIRENTRY())
}
// Copy PNG
for i := 0; i < pngc; i++ {
ico.Write(pngs[i].Read())
}
}
16 changes: 16 additions & 0 deletions helper/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
The MIT License
Copyright (c) 2020 John Siu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package helper

// Debug bool to control log
var Debug bool = false

0 comments on commit 9962189

Please sign in to comment.