Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
octu0 committed Nov 1, 2022
0 parents commit c78a582
Show file tree
Hide file tree
Showing 21 changed files with 947 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.swp
.DS_Store

*.out
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2022, Yusuke Hata
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
208 changes: 208 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# `go-libjpeg-turbo`

[![License](https://img.shields.io/github/license/octu0/go-libjpeg-turbo)](https://github.com/octu0/go-libjpeg-turbo/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/octu0/go-libjpeg-turbo?status.svg)](https://godoc.org/github.com/octu0/go-libjpeg-turbo)
[![Go Report Card](https://goreportcard.com/badge/github.com/octu0/go-libjpeg-turbo)](https://goreportcard.com/report/github.com/octu0/go-libjpeg-turbo)
[![Releases](https://img.shields.io/github/v/release/octu0/go-libjpeg-turbo)](https://github.com/octu0/go-libjpeg-turbo/releases)

Go bindings for [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo)

## Requirements

requires libjpeg-turbo [install](https://github.com/libjpeg-turbo/libjpeg-turbo) on your system

## Usage

### Decode

```go
package main

import (
"fmt"
"image"
"image/png"
"io"
"os"

"github.com/octu0/go-libjpeg-turbo"
)

func main() {
data, err := readFile(“/path/to/image.jpg”)
if err != nil {
panic(err)
}

// create Decoder
decoder, err := turbojpeg.CreateDecoder()
if err != nil {
panic(err)
}
defer decoder.Close()

// decode Header only
header, err := decoder.DecodeHeader(data)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", header)
// => {Width:320 Height:240 Subsampling:2 ColorSpace:1}

// decode JPEG image To RGBA
img, err := decoder.Decode(data, turbojpeg.PixelFormatRGBA)
if err != nil {
panic(err)
}
defer img.Close()

rgba := &image.RGBA{
Pix: img.Bytes(), // decoded image
Stride: img.Width * 4, // 4 = r + g + b + a
Rect: image.Rect(0, 0, img.Width, img.Height),
}

saveImage(rgba)
}

func readFile(path string) ([]byte, error) {
f, err := os.Open("./testdata/src.jpg")
if err != nil {
return nil, err
}
defer f.Close()

data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}

func saveImage(img *image.RGBA) {
out, err := os.CreateTemp("/tmp", "out*.png")
if err != nil {
panic(err)
}
defer out.Close()

if err := png.Encode(out, rgba); err != nil {
panic(err)
}
fmt.Println("png out =", out.Name())
}
```

### Encode

```go
package main

import (
"fmt"
"image"
"image/color"
"image/png"
"io"
"os"

"github.com/octu0/go-libjpeg-turbo"
)

func main() {
rgba, err := readFileRGBA("./testdata/src.png")
if err != nil {
panic(err)
}

out, err := os.CreateTemp("/tmp", "out*.jpg")
if err != nil {
panic(err)
}
defer out.Close()

// create Encoder
encoder, err := turbojpeg.CreateEncoder()
if err != nil {
panic(err)
}
defer encoder.Close()

// encode jpeg From *image.RGBA
if _, err := encoder.EncodeRGBA(out, rgba, 85); err != nil {
panic(err)
}

fmt.Println("jpg out =", out.Name())
}

func readFileRGBA(path string) (*image.RGBA, error) {
f, err := os.Open("./testdata/src.png")
if err != nil {
return nil, err
}
defer f.Close()

rgba, err := PNGToRGBA(f)
if err != nil {
return nil, err
}
return rgba, nil
}

func PNGToRGBA(r io.Reader) (*image.RGBA, error) {
img, err := png.Decode(r)
if err != nil {
return nil, err
}
if i, ok := img.(*image.RGBA); ok {
return i, nil
}

b := img.Bounds()
rgba := image.NewRGBA(b)
for y := b.Min.Y; y < b.Max.Y; y += 1 {
for x := b.Min.X; x < b.Max.X; x += 1 {
c := color.RGBAModel.Convert(img.At(x, y)).(color.RGBA)
rgba.Set(x, y, c)
}
}
return rgba, nil
}
```

## Benchmark

### Decode

3x faster than [image/jpeg.Decode](https://pkg.go.dev/image/jpeg#Decode)

```
goos: darwin
goarch: amd64
pkg: github.com/octu0/go-libjpeg-turbo
cpu: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
BenchmarkJpegDecode
BenchmarkJpegDecode/image/jpeg.Decode
BenchmarkJpegDecode/image/jpeg.Decode-4 436 2664416 ns/op 136977 B/op 19 allocs/op
BenchmarkJpegDecode/turbojpeg.Decode
BenchmarkJpegDecode/turbojpeg.Decode-4 1402 822829 ns/op 332926 B/op 3 allocs/op
PASS
```

### Encode

7x faster than [image/jpeg.Encode](https://pkg.go.dev/image/jpeg#Encode)

```
goos: darwin
goarch: amd64
pkg: github.com/octu0/go-libjpeg-turbo
cpu: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
BenchmarkJpegEncode
BenchmarkJpegEncode/image/jpeg.Encode
BenchmarkJpegEncode/image/jpeg.Encode-4 406 3316061 ns/op 4400 B/op 4 allocs/op
BenchmarkJpegEncode/turbojpeg.Encode
BenchmarkJpegEncode/turbojpeg.Encode-4 3628 357853 ns/op 21776 B/op 2 allocs/op
PASS
```
59 changes: 59 additions & 0 deletions _example/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"fmt"
"image"
"image/png"
"io"
"os"

"github.com/octu0/go-libjpeg-turbo"
)

func main() {
f, err := os.Open("./testdata/src.jpg")
if err != nil {
panic(err)
}
defer f.Close()

data, err := io.ReadAll(f)
if err != nil {
panic(err)
}

decoder, err := turbojpeg.CreateDecoder()
if err != nil {
panic(err)
}
defer decoder.Close()

header, err := decoder.DecodeHeader(data)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", header)

img, err := decoder.Decode(data, turbojpeg.PixelFormatRGBA)
if err != nil {
panic(err)
}
defer img.Close()

rgba := &image.RGBA{
Pix: img.Bytes(),
Stride: img.Width * 4,
Rect: image.Rect(0, 0, img.Width, img.Height),
}

out, err := os.CreateTemp("/tmp", "out*.png")
if err != nil {
panic(err)
}
defer out.Close()

if err := png.Encode(out, rgba); err != nil {
panic(err)
}
fmt.Println("png out =", out.Name())
}
63 changes: 63 additions & 0 deletions _example/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"fmt"
"image"
"image/color"
"image/png"
"io"
"os"

"github.com/octu0/go-libjpeg-turbo"
)

func main() {
f, err := os.Open("./testdata/src.png")
if err != nil {
panic(err)
}
defer f.Close()

rgba, err := PNGToRGBA(f)
if err != nil {
panic(err)
}

out, err := os.CreateTemp("/tmp", "out*.jpg")
if err != nil {
panic(err)
}
defer out.Close()

encoder, err := turbojpeg.CreateEncoder()
if err != nil {
panic(err)
}
defer encoder.Close()

if _, err := encoder.EncodeRGBA(out, rgba, 85); err != nil {
panic(err)
}

fmt.Println("jpg out =", out.Name())
}

func PNGToRGBA(r io.Reader) (*image.RGBA, error) {
img, err := png.Decode(r)
if err != nil {
return nil, err
}
if i, ok := img.(*image.RGBA); ok {
return i, nil
}

b := img.Bounds()
rgba := image.NewRGBA(b)
for y := b.Min.Y; y < b.Max.Y; y += 1 {
for x := b.Min.X; x < b.Max.X; x += 1 {
c := color.RGBAModel.Convert(img.At(x, y)).(color.RGBA)
rgba.Set(x, y, c)
}
}
return rgba, nil
}
10 changes: 10 additions & 0 deletions _example/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/octu0/go-libjpeg-turbo-example

go 1.19

require (
github.com/octu0/go-libjpeg-turbo v0.0.0
github.com/pkg/errors v0.9.1 // indirect
)

replace github.com/octu0/go-libjpeg-turbo => ../
2 changes: 2 additions & 0 deletions _example/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Binary file added _example/testdata/src.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _example/testdata/src.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c78a582

Please sign in to comment.