1
0
mirror of https://github.com/coalaura/ffwebp.git synced 2025-07-17 13:54:36 +00:00

Initial commit

This commit is contained in:
Laura
2024-09-08 00:32:52 +02:00
commit fbb5e12442
24 changed files with 943 additions and 0 deletions

75
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: Build and Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [windows, linux, darwin]
goarch: [amd64, arm64]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23.1'
- name: Set up Environment
run: |
mkdir -p build
echo "package main" > version.go
echo "const Version = \"${{ github.ref_name }}\"" >> version.go
go install mvdan.cc/garble@latest
- name: Build for ${{ matrix.goos }}_${{ matrix.goarch }}
run: |
if [ "${{ matrix.goos }}" = "windows" ]; then EXT=".exe"; else EXT=""; fi
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} garble -tiny build -o build/ffwebp_${{ github.ref_name }}_${{ matrix.goos }}_${{ matrix.goarch }}$EXT -ldflags -w
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: ffwebp_${{ github.ref_name }}_${{ matrix.goos }}_${{ matrix.goarch }}
path: ./build/ffwebp_${{ github.ref_name }}_${{ matrix.goos }}_${{ matrix.goarch }}*
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: ./build
- name: Upload all built binaries to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./build/
asset_name: ffwebp_${{ github.ref_name }}
asset_content_type: application/octet-stream

69
ffwebp_test.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
"bytes"
"image/png"
"log"
"os/exec"
"path/filepath"
"strings"
"testing"
)
var (
TestFiles = []string{
"test/image.avif",
"test/image.bmp",
"test/image.gif",
"test/image.heic",
"test/image.heif",
"test/image.ico",
"test/image.jpg",
"test/image.png",
"test/image.tif",
"test/image.tiff",
"test/image.webp",
"test/image.jxl",
}
)
func TestFFWebP(t *testing.T) {
exe, err := filepath.Abs("bin/ffwebp.exe")
if err != nil {
log.Fatalf("Failed to get absolute path for ffwebp.exe: %v\n", err)
}
for _, file := range TestFiles {
log.Printf("Testing file: %s\n", file)
cmd := exec.Command(exe, "-i", file, "-f", "png", "-s")
// Capture the output (which is expected to be a PNG image)
var stdout bytes.Buffer
cmd.Stdout = &stdout
// Run the command
err := cmd.Run()
if err != nil {
out := strings.TrimSpace(stdout.String())
log.Println(" - FAILED")
log.Fatalf("Test failed for file: %s (%s)\n", file, out)
}
// Decode the captured stdout output as a PNG image
img, err := png.Decode(&stdout)
if err != nil {
log.Println(" - FAILED")
log.Fatalf("Failed to decode PNG image: %v\n", err)
}
if img == nil {
log.Println(" - FAILED")
log.Fatalf("No image data returned for file: %s\n", file)
}
log.Println(" - PASSED")
}
}

187
flags.go Normal file
View File

@ -0,0 +1,187 @@
package main
import (
"os"
"strconv"
"strings"
)
type Argument struct {
IsNil bool
Name string
Value string
}
type Arguments struct {
Arguments map[string]Argument
}
var (
arguments Arguments
)
// I don't like golang flags package
func init() {
arguments = Arguments{
Arguments: make(map[string]Argument),
}
var (
arg string
val string
index int
current Argument
)
for i := 1; i < len(os.Args); i++ {
arg = os.Args[i]
if arg[0] == '-' && len(arg) > 1 {
if arg[1] == '-' {
index = strings.Index(arg[2:], "=")
if index >= 0 {
val = ""
if index+1 < len(arg) {
val = arg[2+index+1:]
}
arguments.Set(Argument{
Name: arg[2 : 2+index],
Value: val,
})
} else {
arguments.Set(Argument{
Name: arg[2:],
})
}
current = Argument{}
} else {
current = Argument{
Name: arg[1:],
}
}
} else {
current.Value = arg
arguments.Set(current)
current = Argument{}
}
}
if current.Name != "" {
arguments.Set(current)
}
}
func (a *Arguments) Set(arg Argument) {
a.Arguments[arg.Name] = arg
}
func (a *Arguments) Get(short, long string) Argument {
arg, ok := a.Arguments[short]
if !ok && long != short {
arg, ok = a.Arguments[long]
}
if !ok {
return Argument{
IsNil: true,
Name: long,
}
}
return arg
}
func (a *Arguments) GetString(short, long string) string {
return a.Get(short, long).String()
}
func (a *Arguments) GetBool(short, long string, def bool) bool {
return a.Get(short, long).Bool(def)
}
func (a *Arguments) GetInt64(short, long string, def, min, max int64) int64 {
return a.Get(short, long).Int64(def, min, max)
}
func (a *Arguments) GetUint64(short, long string, def, min, max uint64) uint64 {
return a.Get(short, long).Uint64(def, min, max)
}
func (a *Arguments) GetFloat64(short, long string, def, min, max float64) float64 {
return a.Get(short, long).Float64(def, min, max)
}
func (a Argument) String() string {
return a.Value
}
func (a Argument) Bool(def bool) bool {
if a.IsNil {
return def
}
if a.Value == "false" || a.Value == "0" {
return false
}
return true
}
func (a Argument) Int64(def, min, max int64) int64 {
if a.IsNil {
return def
}
i, err := strconv.ParseInt(a.Value, 10, 64)
if err != nil {
return def
}
return minmax(i, min, max)
}
func (a Argument) Uint64(def, min, max uint64) uint64 {
if a.IsNil {
return def
}
i, err := strconv.ParseUint(a.Value, 10, 64)
if err != nil {
return def
}
return minmax(i, min, max)
}
func (a Argument) Float64(def, min, max float64) float64 {
if a.IsNil {
return def
}
i, err := strconv.ParseFloat(a.Value, 64)
if err != nil {
return def
}
return minmax(i, min, max)
}
func minmax[T int64 | uint64 | float64](val, min, max T) T {
if min != 0 && val < min {
return min
}
if max != 0 && val > max {
return max
}
return val
}

177
format.go Normal file
View File

@ -0,0 +1,177 @@
package main
import (
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
"github.com/biessek/golang-ico"
"github.com/gen2brain/avif"
"github.com/gen2brain/heic"
"github.com/gen2brain/jpegxl"
"github.com/gen2brain/webp"
"golang.org/x/image/bmp"
"golang.org/x/image/tiff"
)
var (
OutputFormats = []string{
"jpeg",
"png",
"webp",
"gif",
"bmp",
"tiff",
"avif",
"jxl",
"ico",
}
InputFormats = []string{
"jpeg",
"png",
"webp",
"gif",
"bmp",
"tiff",
"avif",
"jxl",
"ico",
"heic",
"heif",
}
)
type Decoder func(io.Reader) (image.Image, error)
func GetDecoderFromContent(in *os.File) (Decoder, error) {
buffer := make([]byte, 128)
_, err := in.Read(buffer)
if err != nil {
return nil, err
}
if _, err := in.Seek(0, io.SeekStart); err != nil {
return nil, err
}
if IsJPEG(buffer) {
return jpeg.Decode, nil
} else if IsPNG(buffer) {
return png.Decode, nil
} else if IsGIF(buffer) {
return gif.Decode, nil
} else if IsBMP(buffer) {
return bmp.Decode, nil
} else if IsWebP(buffer) {
return webp.Decode, nil
} else if IsTIFF(buffer) {
return tiff.Decode, nil
} else if IsICO(buffer) {
return ico.Decode, nil
} else if IsHEIC(buffer) {
return heic.Decode, nil
} else if IsAVIF(buffer) {
return avif.Decode, nil
} else if IsJpegXL(buffer) {
return jpegxl.Decode, nil
}
return nil, fmt.Errorf("unsupported input format")
}
func IsJPEG(buffer []byte) bool {
return len(buffer) > 2 && buffer[0] == 0xFF && buffer[1] == 0xD8
}
func IsPNG(buffer []byte) bool {
return len(buffer) > 8 && string(buffer[:8]) == "\x89PNG\r\n\x1a\n"
}
func IsGIF(buffer []byte) bool {
return len(buffer) > 6 && (string(buffer[:6]) == "GIF87a" || string(buffer[:6]) == "GIF89a")
}
func IsBMP(buffer []byte) bool {
return len(buffer) > 2 && string(buffer[:2]) == "BM"
}
func IsICO(buffer []byte) bool {
return len(buffer) > 4 && buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x01 && buffer[3] == 0x00
}
func IsWebP(buffer []byte) bool {
// Check if its VP8L
if len(buffer) > 16 && string(buffer[12:16]) == "VP8L" {
return true
}
// Check if its WebP or RIFF WEBP
return len(buffer) > 12 && string(buffer[:4]) == "RIFF" && string(buffer[8:12]) == "WEBP"
}
func IsAVIF(buffer []byte) bool {
return len(buffer) > 12 && string(buffer[4:8]) == "ftyp" && string(buffer[8:12]) == "avif"
}
func IsTIFF(buffer []byte) bool {
return len(buffer) > 4 && (string(buffer[:4]) == "II*\x00" || string(buffer[:4]) == "MM\x00*")
}
func IsHEIC(buffer []byte) bool {
return len(buffer) > 12 && string(buffer[4:8]) == "ftyp" && (string(buffer[8:12]) == "heic" || string(buffer[8:12]) == "heix")
}
func IsJpegXL(buffer []byte) bool {
// Check for JPEG XL codestream (starts with 0xFF 0x0A)
if len(buffer) > 2 && buffer[0] == 0xFF && buffer[1] == 0x0A {
return true
}
// Check for JPEG XL container (starts with "JXL ")
return len(buffer) > 12 && string(buffer[:4]) == "JXL "
}
func OutputFormatFromPath(path string) string {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".webp", ".riff":
return "webp"
case ".jpg", ".jpeg":
return "jpeg"
case ".png":
return "png"
case ".gif":
return "gif"
case ".bmp":
return "bmp"
case ".tiff", ".tif":
return "tiff"
case ".avif", ".avifs":
return "avif"
case ".jxl":
return "jxl"
case ".ico":
return "ico"
}
return "webp"
}
func IsValidOutputFormat(format string) bool {
for _, f := range OutputFormats {
if f == format {
return true
}
}
return false
}

19
go.mod Normal file
View File

@ -0,0 +1,19 @@
module ffwebp
go 1.23.1
require (
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
github.com/gen2brain/avif v0.3.2
github.com/gen2brain/heic v0.3.1
github.com/gen2brain/jpegxl v0.3.1
github.com/gen2brain/webp v0.4.5
golang.org/x/image v0.20.0
)
require (
github.com/ebitengine/purego v0.7.1 // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
)

20
go.sum Normal file
View File

@ -0,0 +1,20 @@
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/gen2brain/avif v0.3.2 h1:XUR0CBl5n4ISFJE8/pc1RMEKt5KUVoW8InctN+M7+DQ=
github.com/gen2brain/avif v0.3.2/go.mod h1:tdL2sV6oOJXBZZvT5iP55VEM1X2c3/yJmYKMJTl8fXg=
github.com/gen2brain/heic v0.3.1 h1:ClY5YTdXdIanw7pe9ZVUM9XcsqH6CCCa5CZBlm58qOs=
github.com/gen2brain/heic v0.3.1/go.mod h1:m2sVIf02O7wfO8mJm+PvE91lnq4QYJy2hseUon7So10=
github.com/gen2brain/jpegxl v0.3.1 h1:QAcs68WXQUQRABPVu5p5MineuqfqnVd/JRiI+s7AEE4=
github.com/gen2brain/jpegxl v0.3.1/go.mod h1:jLh4Fl9QaHkc1RsOJu4S2r20x+gSzjnuM+K8jOm4DEo=
github.com/gen2brain/webp v0.4.5 h1:wolsWSKnYfnYaWUtGLx3EfXhLWVvVx9yZGof+JNGYgY=
github.com/gen2brain/webp v0.4.5/go.mod h1:giUCZaJt7D8ae9AjSq4gC3QKUuA9SD8LZy0o2zcWxMI=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

48
help.go Normal file
View File

@ -0,0 +1,48 @@
package main
import (
"fmt"
"os"
"sort"
"strings"
)
func help() {
if !arguments.GetBool("h", "help", false) {
return
}
info(" __ __ _")
info(" / _|/ _| | |")
info("| |_| |___ _____| |__ _ __")
info("| _| _\\ \\ /\\ / / _ \\ '_ \\| '_ \\")
info("| | | | \\ V V / __/ |_) | |_) |")
info("|_| |_| \\_/\\_/ \\___|_.__/| .__/")
info(" | |")
info(" %s |_|", Version)
info("\nffwebp -i <input> [output] [options]")
var max int
for name := range options {
if len(name) > max {
max = len(name)
}
}
var formatted []string
for name, help := range options {
formatted = append(formatted, fmt.Sprintf(" - %-*s: %s", max, name, help))
}
sort.Strings(formatted)
info(strings.Join(formatted, "\n"))
info("\nInput formats: %s", strings.Join(InputFormats, ", "))
info("Output formats: %s", strings.Join(OutputFormats, ", "))
os.Exit(0)
}

91
image.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"os"
"github.com/biessek/golang-ico"
"github.com/gen2brain/avif"
"github.com/gen2brain/jpegxl"
"github.com/gen2brain/webp"
"golang.org/x/image/bmp"
"golang.org/x/image/tiff"
)
var (
options = map[string]string{
"c / colors": "Number of colors (1-256) (gif)",
"e / effort": "Encoder effort level (0-10) (jxl)",
"f / format": "Output format (avif, bmp, gif, jpeg, jxl, png, tiff, webp)",
"h / help": "Show this help page",
"l / lossless": "Use lossless compression (webp)",
"m / method": "Encoder method (0=fast, 6=slower-better) (webp)",
"r / ratio": "YCbCr subsample-ratio (0=444, 1=422, 2=420, 3=440, 4=411, 5=410) (avif)",
"s / silent": "Do not print any output",
"q / quality": "Set quality (0-100) (avif, jpeg, jxl, webp)",
"x / exact": "Preserve RGB values in transparent area (webp)",
"z / compression": "Compression type (0=uncompressed, 1=deflate, 2=lzw, 3=ccittgroup3, 4=ccittgroup4) (tiff)",
}
)
func ReadImage(input *os.File) (image.Image, error) {
decoder, err := GetDecoderFromContent(input)
if err != nil {
return nil, err
}
return decoder(input)
}
func WriteImage(output *os.File, img image.Image, format string) error {
switch format {
case "webp":
options := GetWebPOptions()
LogWebPOptions(options)
return webp.Encode(output, img, options)
case "jpeg":
options := GetJpegOptions()
LogJpegOptions(options)
return jpeg.Encode(output, img, options)
case "png":
return png.Encode(output, img)
case "gif":
options := GetGifOptions()
LogGifOptions(options)
return gif.Encode(output, img, options)
case "bmp":
return bmp.Encode(output, img)
case "tiff":
options := GetTiffOptions()
LogTiffOptions(options)
return tiff.Encode(output, img, options)
case "avif":
options := GetAvifOptions()
LogAvifOptions(options)
return avif.Encode(output, img, options)
case "jxl":
options := GetJxlOptions()
LogJxlOptions(options)
jpegxl.Encode(output, img, options)
case "ico":
return ico.Encode(output, img)
}
return fmt.Errorf("unsupported output format: %s", format)
}

24
log.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"os"
)
var (
silent bool
)
func info(fm string, args ...interface{}) {
if silent {
return
}
fmt.Printf(fm+"\n", args...)
}
func fatalf(code int, fm string, args ...interface{}) {
fmt.Printf("ERROR: "+fm+"\n", args...)
os.Exit(code)
}

81
main.go Normal file
View File

@ -0,0 +1,81 @@
package main
import (
"os"
)
func main() {
help()
silent = arguments.GetBool("s", "silent", false)
// Read input file
input := arguments.GetString("i", "input")
var in *os.File
if input == "" {
in = os.Stdin
} else {
var err error
in, err = os.OpenFile(input, os.O_RDONLY, 0)
if err != nil {
fatalf(1, "Failed to open input file: %s", err)
}
}
// Read image
if in == os.Stdin {
info("Decoding input from stdin...")
} else {
info("Decoding input image...")
}
img, err := ReadImage(in)
if err != nil {
fatalf(4, "Failed to read image: %s", err)
}
// Read output format
format := arguments.GetString("f", "format")
// Read output file
output := arguments.GetString("", "")
var out *os.File
if output == "" {
if format == "" {
format = "webp"
}
out = os.Stdout
silent = true
} else {
var err error
if format == "" {
format = OutputFormatFromPath(output)
}
out, err = os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
fatalf(2, "Failed to open output file: %s", err)
}
}
if !IsValidOutputFormat(format) {
fatalf(3, "Invalid output format: %s", format)
}
info("Using output format: %s", format)
// Write image
info("Encoding output image...")
err = WriteImage(out, img, format)
if err != nil {
fatalf(5, "Failed to write image: %s", err)
}
}

149
options.go Normal file
View File

@ -0,0 +1,149 @@
package main
import (
"image"
"image/gif"
"image/jpeg"
"github.com/gen2brain/avif"
"github.com/gen2brain/jpegxl"
"github.com/gen2brain/webp"
"golang.org/x/image/tiff"
)
func GetWebPOptions() webp.Options {
return webp.Options{
Lossless: arguments.GetBool("l", "lossless", false),
Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
Method: int(arguments.GetUint64("m", "method", 4, 0, 6)),
Exact: arguments.GetBool("x", "exact", false),
}
}
func LogWebPOptions(options webp.Options) {
info("Using output options:")
info(" - lossless: %v", options.Lossless)
info(" - quality: %v", options.Quality)
info(" - method: %v", options.Method)
info(" - exact: %v", options.Exact)
}
func GetJpegOptions() *jpeg.Options {
return &jpeg.Options{
Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
}
}
func LogJpegOptions(options *jpeg.Options) {
info("Using output options:")
info(" - quality: %v", options.Quality)
}
func GetGifOptions() *gif.Options {
return &gif.Options{
NumColors: int(arguments.GetUint64("c", "colors", 256, 0, 256)),
}
}
func LogGifOptions(options *gif.Options) {
info("Using output options:")
info(" - colors: %v", options.NumColors)
}
func GetTiffOptions() *tiff.Options {
return &tiff.Options{
Compression: GetTiffCompressionType(),
}
}
func LogTiffOptions(options *tiff.Options) {
info("Using output options:")
info(" - compression: %s", TiffCompressionTypeToString(options.Compression))
}
func GetAvifOptions() avif.Options {
return avif.Options{
Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
QualityAlpha: int(arguments.GetUint64("qa", "quality-alpha", 100, 0, 100)),
Speed: int(arguments.GetUint64("s", "speed", 6, 0, 10)),
ChromaSubsampling: GetAvifYCbCrSubsampleRatio(),
}
}
func LogAvifOptions(options avif.Options) {
info("Using output options:")
info(" - quality: %v", options.Quality)
info(" - quality-alpha: %v", options.QualityAlpha)
info(" - speed: %v", options.Speed)
info(" - chroma subsampling: %s", options.ChromaSubsampling.String())
}
func GetJxlOptions() jpegxl.Options {
return jpegxl.Options{
Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
Effort: int(arguments.GetUint64("e", "effort", 7, 0, 10)),
}
}
func LogJxlOptions(options jpegxl.Options) {
info("Using output options:")
info(" - quality: %v", options.Quality)
info(" - effort: %v", options.Effort)
}
func GetTiffCompressionType() tiff.CompressionType {
compression := arguments.GetUint64("z", "compression", 1, 0, 4)
switch compression {
case 0:
return tiff.Uncompressed
case 1:
return tiff.Deflate
case 2:
return tiff.LZW
case 3:
return tiff.CCITTGroup3
case 4:
return tiff.CCITTGroup4
}
return tiff.Deflate
}
func TiffCompressionTypeToString(compression tiff.CompressionType) string {
switch compression {
case tiff.Uncompressed:
return "uncompressed"
case tiff.Deflate:
return "deflate"
case tiff.LZW:
return "lzw"
case tiff.CCITTGroup3:
return "ccittgroup3"
case tiff.CCITTGroup4:
return "ccittgroup4"
default:
return "unknown"
}
}
func GetAvifYCbCrSubsampleRatio() image.YCbCrSubsampleRatio {
sampleRatio := arguments.GetUint64("r", "sample-ratio", 0, 0, 5)
switch sampleRatio {
case 0:
return image.YCbCrSubsampleRatio444
case 1:
return image.YCbCrSubsampleRatio422
case 2:
return image.YCbCrSubsampleRatio420
case 3:
return image.YCbCrSubsampleRatio440
case 4:
return image.YCbCrSubsampleRatio411
case 5:
return image.YCbCrSubsampleRatio410
}
return image.YCbCrSubsampleRatio444
}

BIN
test/image.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
test/image.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

BIN
test/image.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

BIN
test/image.heic Normal file

Binary file not shown.

BIN
test/image.heif Normal file

Binary file not shown.

BIN
test/image.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
test/image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

BIN
test/image.jxl Normal file

Binary file not shown.

BIN
test/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

BIN
test/image.tif Normal file

Binary file not shown.

BIN
test/image.tiff Normal file

Binary file not shown.

BIN
test/image.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

3
version.go Normal file
View File

@ -0,0 +1,3 @@
package main
const Version = "development"